/* * SPICTRL SPI driver implmenetation * * COPYRIGHT (c) 2009. * Cobham Gaisler AB. * * The license and distribution terms for this file may be * found in the file LICENSE in this distribution or at * http://www.rtems.com/license/LICENSE. */ #include #include #include #include #include #include #include #include #include #include #include #include /*#define DEBUG 1*/ #ifdef DEBUG #define DBG(x...) printk(x) #define STATIC #else #define DBG(x...) #define STATIC static #endif /*** CAPABILITY REGISTER 0x00 ***/ #define SPICTRL_CAP_SSSZ_BIT 24 #define SPICTRL_CAP_AMODE_BIT 18 #define SPICTRL_CAP_ASELA_BIT 17 #define SPICTRL_CAP_SSEN_BIT 16 #define SPICTRL_CAP_FDEPTH_BIT 8 #define SPICTRL_CAP_REV_BIT 0 #define SPICTRL_CAP_SSSZ (0xff << SPICTRL_CAP_SSSZ_BIT) #define SPICTRL_CAP_AMODE (1<minor_drv, dev->parent->dev->name); priv = dev->priv = malloc(sizeof(struct spictrl_priv)); if ( !priv ) return DRVMGR_NOMEM; memset(priv, 0, sizeof(*priv)); priv->dev = dev; /* This core will not find other cores, so we wait for init2() */ return DRVMGR_OK; } int spictrl_init3(struct drvmgr_dev *dev) { struct spictrl_priv *priv; char prefix[32]; char devName[32]; int rc; priv = (struct spictrl_priv *)dev->priv; /* Do initialization */ /* Initialize i2c library */ rc = rtems_libi2c_initialize(); if (rc != 0) { DBG("SPICTRL: rtems_libi2c_initialize failed, exiting...\n"); free(dev->priv); dev->priv = NULL; return DRVMGR_FAIL; } /* I/O system registered and initialized * Now we take care of device initialization. */ /* Get frequency */ if ( drvmgr_freq_get(dev, DEV_APB_SLV, &priv->core_freq_hz) ) { return DRVMGR_FAIL; } if ( spictrl_device_init(priv) ) { free(dev->priv); dev->priv = NULL; return DRVMGR_FAIL; } /* Get Filesystem name prefix */ prefix[0] = '\0'; if ( drvmgr_get_dev_prefix(dev, prefix) ) { /* Failed to get prefix, make sure of a unique FS name * by using the driver minor. */ sprintf(devName, "/dev/spi%d", dev->minor_drv+1); } else { /* Got special prefix, this means we have a bus prefix * And we should use our "bus minor" */ sprintf(devName, "/dev/%sspi%d", prefix, dev->minor_bus+1); } /* Register Bus for this Device */ rc = rtems_libi2c_register_bus(devName, &priv->i2clib_desc); if (rc < 0) { DBG("SPICTRL: rtems_libi2c_register_bus(%s) failed\n", devName); free(dev->priv); dev->priv = NULL; return DRVMGR_FAIL; } priv->minor = rc; return DRVMGR_OK; } /******************* Driver Implementation ***********************/ STATIC rtems_status_code spictrl_libi2c_send_addr(rtems_libi2c_bus_t *bushdl, uint32_t addr, int rw); /* Set as high frequency of SCK as possible but not higher than * requested frequency (freq). */ static int spictrl_set_freq(struct spictrl_priv *priv, unsigned int freq) { unsigned int core_freq_hz = priv->core_freq_hz; unsigned int lowest_freq_possible; unsigned int div, div16, pm, fact; /* Lowest possible when DIV16 is set and PM is 0xf */ lowest_freq_possible = core_freq_hz / (16 * 4 * (0xf + 1)); if ( freq < lowest_freq_possible ) { DBG("SPICTRL: TOO LOW FREQ %u, CORE FREQ %u, LOWEST FREQ %u\n", freq, core_freq_hz, lowest_freq_possible); return -1; } div = ((core_freq_hz / 2) + (freq-1)) / freq; DBG("SPICTRL: DIV=%d, FREQ=%d\n", div, freq); /* Is DIV16 neccessary? */ if ( div > 16 ) { div = (div + (16 - 1)) / 16; div16 = 1; } else { div16 = 0; } if ( div > 0xf ) { fact = 0; /* FACT adds an factor /2 */ div = (div + (2 - 1)) / 2; } else { fact = 1; } pm = div-1; /* Update hardware */ priv->regs->mode = (priv->regs->mode & ~(SPICTRL_MODE_PM|SPICTRL_MODE_DIV16|SPICTRL_MODE_FACT)) | (pm << SPICTRL_MODE_PM_BIT) | (div16 << SPICTRL_MODE_DIV16_BIT) | (fact << SPICTRL_MODE_FACT_BIT); DBG("SPICTRL: Effective bit rate %u (requested %u), PM: %x, FACT: %d, div16: %x, core_freq: %u\n", core_freq_hz / (2 * (fact ? 1 : 2) * (div) * (div16 ? 16 : 1)), freq, pm, fact, div16, core_freq_hz); return 0; } /* Start Automated Periodic transfers, after this call read can be done */ static int spictrl_start_periodic(struct spictrl_priv *priv) { struct spictrl_ioctl_config *cfg = &priv->periodic_cfg; unsigned int am_cfg; /* Clear the events */ priv->regs->event = 0xffffffff; /* Enable core */ priv->regs->mode |= SPICTRL_MODE_EN | SPICTRL_MODE_MS; /* Update hardware config from flags and period */ priv->regs->am_period = cfg->period; /* Remove SPICTRL_PERIOD_FLAGS_ASEL and ACT bit and shift into posistion */ am_cfg = (cfg->period_flags & 0x1f8) >> 1; priv->regs->am_cfg = am_cfg; /* Start automated periodic transfers */ if ( cfg->period_flags & SPICTRL_PERIOD_FLAGS_EACT ) { /* Enable external triggering */ priv->regs->am_cfg = am_cfg | SPICTRL_AMCFG_EACT; } else { /* Activate periodic transfers */ priv->regs->am_cfg = am_cfg | SPICTRL_AMCFG_ACT; } return 0; } /* Stop Automated Periodic transfers */ static void spictrl_stop_periodic(struct spictrl_priv *priv) { priv->regs->am_cfg = 0; } /* Return the status of the SPI controller (the event register), * it may be needed in periodic mode to look at the Not Full bit (NF) * in order not to hang in an infinte loop when read is called. */ static inline unsigned int spictrl_status(struct spictrl_priv *priv) { return priv->regs->event; } static int spictrl_read_periodic( struct spictrl_priv *priv, struct spictrl_period_io *rarg) { int i, rxi, rxshift, bits_per_char, reg; unsigned int rx_word, mask; void *rxbuf; if ( rarg->options & 0x1 ) { /* Read mask registers */ for (i=0; i<4; i++) { rarg->masks[i] = priv->regs->am_mask[i]; } } if ( rarg->options & 0x2 ) { /* Read receive registers (after updating masks so that the caller can * read current buffer without knowning of actual register mask). */ /* If not started we could be hanging here forever. */ if ( !priv->periodic_started ) return -1; rxshift = priv->rxshift; bits_per_char = priv->bits_per_char; rx_word = 0; rxbuf = rarg->data; if ( !rxbuf ) { /* If no data pointer specified we cannot copy data... */ return -1; } /* Wait until all data is available (if started) */ while ( (priv->regs->event & SPICTRL_EVENT_NE) == 0 ) { ; } rxi = 0; for (i=0; i<4; i++) { mask = rarg->masks[i]; reg = 0; while ( mask ) { if ( mask & 1 ) { /* Update Register */ rx_word = priv->regs->am_rx[i*32 + reg] >> rxshift; if ( bits_per_char <= 8 ) { *((unsigned char *)rxbuf + rxi) = rx_word; } else if ( bits_per_char <= 16 ) { *((unsigned short *)rxbuf + rxi) = rx_word; } else { *((unsigned int *)rxbuf + rxi) = rx_word; } rxi++; } mask = mask>>1; reg++; } } } return 0; } static int spictrl_write_periodic( struct spictrl_priv *priv, struct spictrl_period_io *warg) { int i, txi, txshift, bits_per_char, reg; unsigned int tx_word, mask; void *txbuf; if ( warg->options & 0x2 ) { /* Make sure core is enabled, otherwise TX registers writes are lost */ priv->regs->mode |= SPICTRL_MODE_EN; /* Update Transmit registers (before updating masks so that we do not * transmit invalid data) */ txshift = priv->txshift; bits_per_char = priv->bits_per_char; tx_word = 0; txbuf = warg->data; if ( !txbuf ) { /* If no data pointer specified we fill up with * idle chars. */ tx_word = priv->idle_char << txshift; } txi = 0; for (i=0; i<4; i++) { mask = warg->masks[i]; reg = 0; while ( mask ) { if ( mask & 1 ) { if ( txbuf ) { if ( bits_per_char <= 8 ) { tx_word = *((unsigned char *)txbuf + txi); } else if ( bits_per_char <= 16 ) { tx_word = *((unsigned short *)txbuf + txi); } else { tx_word = *((unsigned int *)txbuf + txi); } tx_word = tx_word << txshift; txi++; } /* Update Register */ DBG("WRITE 0x%08x to 0x%08x\n", tx_word, &priv->regs->am_tx[i*32 + reg]); priv->regs->am_tx[i*32 + reg] = tx_word; } mask = mask>>1; reg++; } } } if ( warg->options & 0x1 ) { /* Update mask registers */ for (i=0; i<4; i++) { DBG("WRITE 0x%08x to 0x%08x (MSK%d)\n", warg->masks[i], &priv->regs->am_mask[i], i); priv->regs->am_mask[i] = warg->masks[i]; } } return 0; } static int spictrl_read_write( struct spictrl_priv *priv, void *rxbuf, void *txbuf, int len) { unsigned int tx_word, rx_word, tmp; int txshift = priv->txshift; int rxshift = priv->rxshift; int txi, rxi, bits_per_char; int length; /* Use IOCTL for periodic reads. The FIFO is not supported in automated * periodic mode */ if ( priv->periodic_cfg.periodic_mode ) { return -1; } bits_per_char = priv->bits_per_char; tx_word = 0; if ( !txbuf ) { tx_word = priv->idle_char << txshift; } /* Clear the events */ priv->regs->event = 0xffffffff; /* Enable core */ priv->regs->mode |= SPICTRL_MODE_EN | SPICTRL_MODE_MS; length = len; if ( bits_per_char > 8 ) { length = length / 2; if ( bits_per_char > 16 ) length = length / 2; } DBG("SPICTRL: LENGTH = %d, Bits/Char: %d, Shift: %d, %d\n", length, bits_per_char, txshift, rxshift); txi=0; rxi=0; while ( (rxi < length) || (txi < length) ) { /* Get transmit word */ if ( length > txi ) { if ( txbuf ) { if ( bits_per_char <= 8 ) { tx_word = *((unsigned char *)txbuf + txi); } else if ( bits_per_char <= 16 ) { tx_word = *((unsigned short *)txbuf + txi); } else { tx_word = *((unsigned int *)txbuf + txi); } tx_word = tx_word << txshift; } /* Wait for SPICTRL to get ready for another TX char */ while ( (priv->regs->event & SPICTRL_EVENT_NF) == 0 ) { /* Wait for all chars to transmit */ /* Could implement waiting for SPICTRL IRQ here */ } DBG("SPICTRL: Writing 0x%x\n", tx_word); /* Transmit word */ priv->regs->tx = tx_word; txi++; } /* Read */ while ( priv->regs->event & SPICTRL_EVENT_NE ) { /* Read to avoid overrun */ tmp = priv->regs->rx; DBG("SPICTRL: Read 0x%x\n", tmp); if ( rxbuf && (length > rxi) ) { /* Copy word to user buffer */ rx_word = (tmp >> rxshift); DBG("SPICTRL: Receiving 0x%x (0x%x, %d)\n", rx_word, tmp, rxshift); if ( bits_per_char <= 8 ) { *((unsigned char *)rxbuf + rxi) = rx_word; } else if ( bits_per_char <= 16 ) { *((unsigned short *)rxbuf + rxi) = rx_word; } else { *((unsigned int *)rxbuf + rxi) = rx_word; } } rxi++; } } return len; } STATIC rtems_status_code spictrl_libi2c_init(rtems_libi2c_bus_t *bushdl) { struct spictrl_priv *priv = (struct spictrl_priv *)bushdl; DBG("SPICTRL: spictrl_libi2c_init\n"); /* Disable SPICTTRL, Select Master mode */ priv->regs->mode = SPICTRL_MODE_MS; /* Mask all Interrupts */ priv->regs->mask = 0; /* Select no slave */ priv->regs->slvsel = 0xffffffff; /* Clear all events */ priv->regs->event = 0xffffffff; return 0; } /* Nothing to be done in start */ STATIC rtems_status_code spictrl_libi2c_send_start(rtems_libi2c_bus_t *bushdl) { DBG("SPICTRL: spictrl_libi2c_send_start\n"); return 0; } /* Inactivate all chip selects, indicates "End of command" */ STATIC rtems_status_code spictrl_libi2c_send_stop(rtems_libi2c_bus_t *bushdl) { struct spictrl_priv *priv = (struct spictrl_priv *)bushdl; priv->regs->slvsel = 0xffffffff; if ( priv->slvSelFunc ) { /* unslect all */ return priv->slvSelFunc(priv->regs, -1, 0); } DBG("SPICTRL: spictrl_libi2c_send_stop\n"); return 0; } /* Select Slave address by selecting apropriate chip select */ STATIC rtems_status_code spictrl_libi2c_send_addr(rtems_libi2c_bus_t *bushdl, uint32_t addr, int rw) { struct spictrl_priv *priv = (struct spictrl_priv *)bushdl; DBG("SPICTRL: spictrl_libi2c_send_addr, %d\n", addr); if ( priv->slvSelFunc ) { /* Let user set spi select using for example GPIO */ return priv->slvSelFunc(priv->regs, addr, 1); } else if ( priv->regs->capability & SPICTRL_CAP_SSEN ) { int slaves; /* Maximum number of slaves the core support */ slaves = (priv->regs->capability & SPICTRL_CAP_SSSZ) >> SPICTRL_CAP_SSSZ_BIT; if ( addr > slaves ) return -1; if ( (priv->regs->capability & SPICTRL_CAP_ASELA) && (priv->periodic_cfg.period_flags & SPICTRL_PERIOD_FLAGS_ASEL) ) { /* When automatic slave select is supported by hardware and * enabled by configuration the SPI address is determined by * the automatic slave select register and the "idle" slave * select register is set by configuration. */ priv->regs->am_slvsel = ~(1<<(addr-1)); priv->regs->slvsel = priv->periodic_cfg.period_slvsel; /* Enable automatic slave select */ priv->regs->mode |= SPICTRL_MODE_ASEL; } else { /* Normal mode */ priv->regs->slvsel = ~(1<<(addr-1)); } } return 0; } /* Read a number of bytes */ STATIC int spictrl_libi2c_read_bytes(rtems_libi2c_bus_t *bushdl, unsigned char *bytes, int nbytes) { struct spictrl_priv *priv = (struct spictrl_priv *)bushdl; int ret; DBG("SPICTRL: spictrl_libi2c_read_bytes %d\n", nbytes); ret = spictrl_read_write(priv, bytes, NULL, nbytes); if ( ret < 0 ) { printf("SPICTRL: Error Reading\n"); } #ifdef DEBUG else { int i; for(i=0; iregs->mode &= ~SPICTRL_MODE_EN; /* Change bit frequency */ if ( spictrl_set_freq(priv, trf_mode->baudrate) ) { /* Unable to set such a low frequency. */ return -1; } /* Set Clock Polarity, Clock Phase, Reverse mode and Word Length */ mode = (priv->regs->mode & ~(SPICTRL_MODE_CPOL|SPICTRL_MODE_CPHA|SPICTRL_MODE_REV|SPICTRL_MODE_LEN)); if ( trf_mode->clock_inv ) mode |= SPICTRL_MODE_CPOL; if ( trf_mode->clock_phs ) mode |= SPICTRL_MODE_CPHA; if ( trf_mode->lsb_first == 0 ) mode |= SPICTRL_MODE_REV; /* Set Reverse mode (MSB first) */ if ( (trf_mode->bits_per_char < 4) || ((trf_mode->bits_per_char > 16) && (trf_mode->bits_per_char != 32)) ) return -1; if ( trf_mode->bits_per_char == 32 ) { priv->txshift = 0; priv->rxshift = 0; } else { mode |= (trf_mode->bits_per_char-1) << SPICTRL_MODE_LEN_BIT; if ( trf_mode->lsb_first == 0 ) { /* REV bit 1 */ priv->txshift = 32 - trf_mode->bits_per_char; priv->rxshift = 16; } else { /* REV bit 0 */ priv->txshift = 0; priv->rxshift = 16 - trf_mode->bits_per_char; } } priv->bits_per_char = trf_mode->bits_per_char; priv->lsb_first = trf_mode->lsb_first; priv->idle_char = trf_mode->idle_char; /* Update hardware */ priv->regs->mode = mode; return 0; } case RTEMS_LIBI2C_IOCTL_READ_WRITE: { rtems_libi2c_read_write_t *arg = buffer; DBG("SPICTRL: IOCTL READ/WRITE, RX: 0x%x, TX: 0x%x, len: %d\n", arg->rd_buf, arg->wr_buf, arg->byte_cnt); #ifdef DEBUG /* Printf out what is going to be transmitted */ if ( arg->wr_buf ) { unsigned char *bytes = (unsigned char *)arg->wr_buf; int i; for(i=0; ibyte_cnt; i+=16) { DBG("0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x ", bytes[0+i], bytes[1+i], bytes[2+i], bytes[3+i], bytes[4+i], bytes[5+i], bytes[6+i], bytes[7+i]); DBG("0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", bytes[8+i], bytes[9+i], bytes[10+i], bytes[11+i], bytes[12+i], bytes[13+i], bytes[14+i], bytes[15+i]); } } #endif ret = spictrl_read_write(priv, arg->rd_buf, (unsigned char *)arg->wr_buf, arg->byte_cnt); #ifdef DEBUG /* Printf out what was read */ if ( arg->rd_buf ) { unsigned char *bytes = (unsigned char *)arg->rd_buf; int i; for(i=0; ibyte_cnt; i+=16) { DBG("0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x ", bytes[0+i], bytes[1+i], bytes[2+i], bytes[3+i], bytes[4+i], bytes[5+i], bytes[6+i], bytes[7+i]); DBG("0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", bytes[8+i], bytes[9+i], bytes[10+i], bytes[11+i], bytes[12+i], bytes[13+i], bytes[14+i], bytes[15+i]); } } #endif return ret; } /* Enable Periodic mode */ case SPICTRL_IOCTL_CONFIG: { struct spictrl_ioctl_config *cfg; DBG("SPICTRL: Configuring Periodic mode\n"); if ( priv->periodic_started ) { DBG("SPICTRL: Periodic mode already started, too late to configure\n"); return -1; } cfg = buffer; if ( cfg == NULL ) { memset(&priv->periodic_cfg, 0, sizeof(priv->periodic_cfg)); } else { priv->periodic_cfg = *cfg; } cfg = &priv->periodic_cfg; if ( cfg->periodic_mode ) { /* Enable Automated Periodic mode */ priv->regs->mode |= SPICTRL_MODE_AMEN; /* Check that hardware has support for periodic mode */ if ( (priv->regs->mode & SPICTRL_MODE_AMEN) == 0 ) { priv->periodic_cfg.periodic_mode = 0; DBG("SPICTRL: Periodic mode not supported by hardware\n"); return -1; } } else { /* Disable Periodic mode */ priv->regs->mode &= ~SPICTRL_MODE_AMEN; } priv->periodic_started = 0; /* Set clockgap and TAC */ priv->regs->mode = (priv->regs->mode & ~(SPICTRL_MODE_CG|SPICTRL_MODE_TAC)) | (cfg->clock_gap << SPICTRL_MODE_CG_BIT) | (cfg->flags & SPICTRL_MODE_TAC); return 0; } case SPICTRL_IOCTL_PERIOD_START: { if ( !priv->periodic_cfg.periodic_mode || priv->periodic_started ) { return -1; } if ( spictrl_start_periodic(priv) == 0 ) { priv->periodic_started = 1; return 0; } else return -1; } case SPICTRL_IOCTL_PERIOD_STOP: { if ( !priv->periodic_cfg.periodic_mode || !priv->periodic_started ) { return -1; } spictrl_stop_periodic(priv); priv->periodic_started = 0; return 0; } case SPICTRL_IOCTL_STATUS: { if ( !buffer ) return 0; *(unsigned int *)buffer = spictrl_status(priv); return 0; } case SPICTRL_IOCTL_PERIOD_WRITE: { if ( !priv->periodic_cfg.periodic_mode || !buffer ) { return -1; } if ( spictrl_write_periodic(priv, (struct spictrl_period_io *) buffer) == 0 ) { return 0; } else return -1; } case SPICTRL_IOCTL_PERIOD_READ: { if ( !priv->periodic_cfg.periodic_mode || !buffer ) { return -1; } if ( spictrl_read_periodic(priv, (struct spictrl_period_io *) buffer) == 0 ) { return 0; } else return -1; } case SPICTRL_IOCTL_REGS: { /* Copy Register Base Address to user space */ if ( !buffer ) { return -1; } *(struct spictrl_regs **)buffer = priv->regs; return 0; } default: /* Unknown IOCTL */ return -1; } return 0; } STATIC rtems_libi2c_bus_ops_t spictrl_libi2c_ops = { .init = spictrl_libi2c_init, .send_start = spictrl_libi2c_send_start, .send_stop = spictrl_libi2c_send_stop, .send_addr = spictrl_libi2c_send_addr, .read_bytes = spictrl_libi2c_read_bytes, .write_bytes = spictrl_libi2c_write_bytes, .ioctl = spictrl_libi2c_ioctl }; int spictrl_device_init(struct spictrl_priv *priv) { struct amba_dev_info *ambadev; struct ambapp_core *pnpinfo; union drvmgr_key_value *value; /* Get device information from AMBA PnP information */ ambadev = (struct amba_dev_info *)priv->dev->businfo; if ( ambadev == NULL ) { return -1; } pnpinfo = &ambadev->info; priv->irq = pnpinfo->irq; priv->regs = (struct spictrl_regs *)pnpinfo->apb_slv->start; priv->fdepth = (priv->regs->capability & SPICTRL_CAP_FDEPTH) >> SPICTRL_CAP_FDEPTH_BIT; DBG("SPCTRL: 0x%x irq %d, FIFO: %d\n", (unsigned int)priv->regs, priv->irq, priv->fdepth); /* Mask all Interrupts */ priv->regs->mask = 0; /* Disable SPICTTRL */ priv->regs->mode = 0; /* Get custom */ value = drvmgr_dev_key_get(priv->dev, "slvSelFunc", KEY_TYPE_POINTER); if ( value ) { priv->slvSelFunc = value->ptr; } /* Prepare I2C layer */ priv->i2clib_desc.ops = &spictrl_libi2c_ops; priv->i2clib_desc.size = sizeof(spictrl_libi2c_ops); return 0; }