--- mips/atheros/ar71xx_spi.c.orig 2012-09-12 16:04:49.993155210 -0300 +++ mips/atheros/ar71xx_spi.c 2012-09-13 10:10:48.363155242 -0300 @@ -51,6 +51,7 @@ #include "spibus_if.h" #include +#include #undef AR71XX_SPI_DEBUG #ifdef AR71XX_SPI_DEBUG @@ -77,7 +78,10 @@ struct ar71xx_spi_softc { device_t sc_dev; struct resource *sc_mem_res; + struct spi_config sc_config; uint32_t sc_reg_ctrl; + int sc_pre_delay; + int sc_post_delay; }; static int @@ -87,6 +91,95 @@ return (0); } +/* + * Configure the hardware clock to achieve the requested frequency without + * going over. If the requested frequency is less than the minimum possible + * hardware frequency, software delays will be used to meet the target. + */ +static int +ar71xx_spi_set_clock(struct ar71xx_spi_softc *sc, unsigned int hz) +{ + unsigned int div; + unsigned int actual_divisor; + unsigned int actual_hz; + unsigned int addl_period_us; + uint64_t temp64; + + if (0 == hz) { + /* Maximum frequency possible. */ + div = 0; + } else { + /* + * Figure the divider value that would achieve the closest + * frequency to that requested without going over. + */ + div = (ar71xx_ahb_freq() - 1) / (2 * hz); + } + + /* Avoid chip bug at 0 == div. */ + if (0 == div) { + div = 1; + } + + if (div > SPI_CTRL_CLOCK_DIVIDER_MAX) { + div = SPI_CTRL_CLOCK_DIVIDER_MAX; + } + + actual_divisor = 2 * (div + 1); + + /* What the hardware will generate, rounded up. */ + actual_hz = (ar71xx_ahb_freq() + actual_divisor - 1) / actual_divisor; + + if (0 == hz) { + hz = actual_hz; + } + + /* + * At requested clock rates below the hardware minimum, figure out + * what delays need to be inserted when feeding bits to the hardware + * in order to stay under the target. + */ + if (actual_hz > hz) { + + /* + * Compute the additional number of microseconds we need in + * the clock period. In the spirit of not exceeding the + * target frequency, round what we need up, round what the + * hardware gives us down. + */ + addl_period_us = (1000000 + hz - 1) / hz + 1000000 / actual_hz; + + sc->sc_pre_delay = addl_period_us / 2; + sc->sc_post_delay = addl_period_us - sc->sc_pre_delay; + + /* + * A high resolution, rounded up inversion of sum of + * hardware and software period components, so one can be + * confident a requested hardware maximum clock rate is not + * being exceeded. + */ + temp64 = 1000000ull * actual_divisor + + (uint64_t)addl_period_us * (uint64_t)ar71xx_ahb_freq(); + + sc->sc_config.clock_hz = + (1000000ull * (uint64_t)ar71xx_ahb_freq() + temp64 - 1) / + temp64; + } else { + sc->sc_pre_delay = 0; + sc->sc_post_delay = 0; + sc->sc_config.clock_hz = ar71xx_ahb_freq() / actual_divisor; + } + +#if 0 + device_printf(sc->sc_dev, "Requested hz=%u reg=0x%02x pre=%u post=%u\n", + hz, div, sc->sc_pre_delay, sc->sc_post_delay); +#endif + + SPI_WRITE(sc, AR71XX_SPI_CTRL, SPI_CTRL_REMAP_DISABLE | div); + + return (0); +} + static int ar71xx_spi_attach(device_t dev) { @@ -105,7 +198,13 @@ SPI_WRITE(sc, AR71XX_SPI_FS, 1); sc->sc_reg_ctrl = SPI_READ(sc, AR71XX_SPI_CTRL); - SPI_WRITE(sc, AR71XX_SPI_CTRL, 0x43); + + /* + * The following is equivalent to AR71XX_SPI_CTRL = 0x43, which has + * been the historical default. + */ + ar71xx_spi_set_clock(sc, (ar71xx_ahb_freq() - 1) / 6); + SPI_WRITE(sc, AR71XX_SPI_IO_CTRL, SPI_IO_CTRL_CSMASK); device_add_child(dev, "spibus", 0); @@ -218,6 +317,28 @@ } static int +ar71xx_spi_get_config(device_t dev, struct spi_config *cfg) +{ + struct ar71xx_spi_softc *sc = device_get_softc(dev); + + *cfg = sc->sc_config; + + return (0); +} + +static int +ar71xx_spi_set_config(device_t dev, struct spi_config *cfg) +{ + struct ar71xx_spi_softc *sc = device_get_softc(dev); + + if (ar71xx_spi_set_clock(sc, cfg->clock_hz)) { + return (ENXIO); + } + + return (0); +} + +static int ar71xx_spi_detach(device_t dev) { struct ar71xx_spi_softc *sc = device_get_softc(dev); @@ -240,6 +361,8 @@ DEVMETHOD(spibus_acquire_bus, ar71xx_spi_acquire_bus), DEVMETHOD(spibus_release_bus, ar71xx_spi_release_bus), DEVMETHOD(spibus_transfer, ar71xx_spi_transfer), + DEVMETHOD(spibus_get_config, ar71xx_spi_get_config), + DEVMETHOD(spibus_set_config, ar71xx_spi_set_config), {0, 0} };