Index: sys/arm/broadcom/bcm2835/bcm2835_spi.c =================================================================== --- sys/arm/broadcom/bcm2835/bcm2835_spi.c +++ sys/arm/broadcom/bcm2835/bcm2835_spi.c @@ -200,6 +200,13 @@ return (bcm_spi_cs_bit_proc(oidp, arg1, arg2, req, SPI_CS_CSPOL1)); } +static int +bcm_spi_cspol2_proc(SYSCTL_HANDLER_ARGS) +{ + + return (bcm_spi_cs_bit_proc(oidp, arg1, arg2, req, SPI_CS_CSPOL2)); +} + static void bcm_spi_sysctl_init(struct bcm_spi_softc *sc) { @@ -228,6 +235,9 @@ SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "cspol1", CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc), bcm_spi_cspol1_proc, "IU", "SPI BUS chip select 1 polarity"); + SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "cspol2", + CTLFLAG_RW | CTLTYPE_UINT, sc, sizeof(*sc), + bcm_spi_cspol2_proc, "IU", "SPI BUS chip select 2 polarity"); } static int @@ -422,7 +432,7 @@ bcm_spi_transfer(device_t dev, device_t child, struct spi_command *cmd) { struct bcm_spi_softc *sc; - uint32_t cs; + uint32_t cs, reg_cs, reg_clk, mode, clock; int err; sc = device_get_softc(dev); @@ -434,18 +444,26 @@ /* Get the proper chip select for this child. */ spibus_get_cs(child, &cs); - - cs &= ~SPIBUS_CS_HIGH; - - if (cs > 2) { + if ((cs & (~SPIBUS_CS_HIGH)) > 2) { device_printf(dev, "Invalid chip select %d requested by %s\n", cs, device_get_nameunit(child)); return (EINVAL); } + /* obtain clock, mode from spibus */ + spibus_get_clock(child, &clock); + spibus_get_mode(child, &mode); + BCM_SPI_LOCK(sc); + /* preserve SPI_CS and SPI_CLK so I can restore them */ + /* these were originally controlled by sysctl vars. */ + /* since spibus and spigen now control these via ivars */ + /* there may no longer be a need for preserving them */ + reg_cs = BCM_SPI_READ(sc, SPI_CS); + reg_clk = BCM_SPI_READ(sc, SPI_CLK); + /* If the controller is in use wait until it is available. */ while (sc->sc_flags & BCM_SPI_BUSY) mtx_sleep(dev, &sc->sc_mtx, 0, "bcm_spi", 0); @@ -465,12 +483,74 @@ sc->sc_len = cmd->tx_cmd_sz + cmd->tx_data_sz; /* + * Assign CS polarity first, while the CS indicates 'inactive'. + * This will need to set the corect polarity bit based on the 'cs', and + * the polarity bit will remain in this state, even after the transaction + * is complete. + */ + if((cs & ~SPIBUS_CS_HIGH) == 0) { + bcm_spi_modifyreg(sc, SPI_CS, + SPI_CS_CSPOL0, + ((cs & (SPIBUS_CS_HIGH)) ? SPI_CS_CSPOL0 : 0)); + } + else if((cs & ~SPIBUS_CS_HIGH) == 1) { + bcm_spi_modifyreg(sc, SPI_CS, + SPI_CS_CSPOL1, + ((cs & (SPIBUS_CS_HIGH)) ? SPI_CS_CSPOL1 : 0)); + } + else if((cs & ~SPIBUS_CS_HIGH) == 2) { + bcm_spi_modifyreg(sc, SPI_CS, + SPI_CS_CSPOL2, + ((cs & (SPIBUS_CS_HIGH)) ? SPI_CS_CSPOL2 : 0)); + } + + /* + * Set the mode in 'SPI_CS' (clock phase and polarity bits). + * This must happen before CS output pin is active. + * Otherwise, you might glitch and drop the first bit. + */ + bcm_spi_modifyreg(sc, SPI_CS, + SPI_CS_CPOL | SPI_CS_CPHA, + ((mode & SPIBUS_MODE_CPHA) ? SPI_CS_CPHA : 0) | + ((mode & SPIBUS_MODE_CPOL) ? SPI_CS_CPOL : 0)); + + /* + * Set the clock divider in 'SPI_CLK - see 'bcm_spi_clock_proc()'. + * If the spibus device has 0 for clock, leave value alone. + * A zero maximum clock implies 'use default' - see 'ofw_spibus.c'. + * Since spibus_get_clock value is a maximum, if reg value is slower, + * keep it. Otherwise, assign the lower frequency. The previous + * register value for the frequency will be restored after the + * transaction is over, so that sysctl vars and overlays can still + * be used to set a maximum frequency when the device is created. + */ + if(clock != 0) { + /* calculate 'clock' as a divider value from freq */ + clock = SPI_CORE_CLK / clock; + if (clock <= 1) + clock = 2; + else if (clock % 2) + clock--; + if (clock > 0xffff) + clock = 0; + + /* + * Test if 'reg_clk' value is less than 'clock'. + * If it is, then the frequency is higher, and clock + * should be assigned as the new divider. + */ + if((reg_clk & 0xffff) < clock) + BCM_SPI_WRITE(sc, SPI_CLK, clock); + } + + /* * Set the CS for this transaction, enable interrupts and announce * we're ready to tx. This will kick off the first interrupt. */ bcm_spi_modifyreg(sc, SPI_CS, SPI_CS_MASK | SPI_CS_TA | SPI_CS_INTR | SPI_CS_INTD, - cs | SPI_CS_TA | SPI_CS_INTR | SPI_CS_INTD); + (cs & (~SPIBUS_CS_HIGH)) | /* cs is the lower 2 bits of the reg */ + SPI_CS_TA | SPI_CS_INTR | SPI_CS_INTD); /* Wait for the transaction to complete. */ err = mtx_sleep(dev, &sc->sc_mtx, 0, "bcm_spi", hz * 2); @@ -478,6 +558,19 @@ /* Make sure the SPI engine and interrupts are disabled. */ bcm_spi_modifyreg(sc, SPI_CS, SPI_CS_TA | SPI_CS_INTR | SPI_CS_INTD, 0); + /* + * Restore the original mode in 'SPI_CS' and clock in 'SPI_CLK' while + * the spi device is still locked. This way a single transaction can + * can specify a clock speed and mode without affecting the defaults + * for the SPI device itself. This is especially important for the + * maximum clock speed, since _not_ restoring it would mess up the + * algorithm that determines whether or not the transaction needs to + * apply a slower clock speed. + */ + bcm_spi_modifyreg(sc, SPI_CS, + SPI_CS_CPOL | SPI_CS_CPHA, reg_cs); + BCM_SPI_WRITE(sc, SPI_CLK, reg_clk); + /* Release the controller and wakeup the next thread waiting for it. */ sc->sc_flags = 0; wakeup_one(dev);