#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/rman.h>
#include <sys/timeet.h>
#include <sys/timetc.h>
#include <sys/watchdog.h>
#include <dev/spibus/spi.h>
#include <dev/spibus/spibusvar.h>
#include "spibus_if.h"
#include <dev/fdt/fdt_common.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <machine/bus.h>
#include <machine/cpu.h>
#include <machine/intr.h>
#define READ4(_sc, _reg) \
bus_space_read_4(_sc->bst, _sc->bsh, _reg)
#define WRITE4(_sc, _reg, _val) \
bus_space_write_4(_sc->bst, _sc->bsh, _reg, _val)
#define SPI_SRR 0x40
#define SRR_RESET 0x0A
#define SPI_CR 0x60
#define CR_LSB_FIRST (1 << 9)
#define CR_MASTER_TI (1 << 8)
#define CR_MSS (1 << 7)
#define CR_RST_RX (1 << 6)
#define CR_RST_TX (1 << 5)
#define CR_CPHA (1 << 4)
#define CR_CPOL (1 << 3)
#define CR_MASTER (1 << 2)
#define CR_SPE (1 << 1)
#define CR_LOOP (1 << 0)
#define SPI_SR 0x64
#define SR_TX_FULL (1 << 3)
#define SR_TX_EMPTY (1 << 2)
#define SR_RX_FULL (1 << 1)
#define SR_RX_EMPTY (1 << 0)
#define SPI_DTR 0x68
#define SPI_DRR 0x6C
#define SPI_SSR 0x70
#define SPI_TFOR 0x74
#define SPI_RFOR 0x78
#define SPI_DGIER 0x1C
#define SPI_IPISR 0x20
#define SPI_IPIER 0x28
struct spi_softc {
struct resource *res[1];
bus_space_tag_t bst;
bus_space_handle_t bsh;
void *ih;
};
static struct ofw_compat_data compat_data[] = {
{ "xlnx,xps-spi-3.2", 1 },
{ "xlnx,xps-spi-2.00.a", 1 },
{ NULL, 0 }
};
static struct resource_spec spi_spec[] = {
{ SYS_RES_MEMORY, 0, RF_ACTIVE },
{ -1, 0 }
};
static int
spi_probe(device_t dev)
{
if (!ofw_bus_status_okay(dev))
return (ENXIO);
if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
return (ENXIO);
device_set_desc(dev, "Xilinx Quad SPI");
return (BUS_PROBE_DEFAULT);
}
static int
spi_attach(device_t dev)
{
struct spi_softc *sc;
uint32_t reg;
sc = device_get_softc(dev);
if (bus_alloc_resources(dev, spi_spec, sc->res)) {
device_printf(dev, "could not allocate resources\n");
return (ENXIO);
}
sc->bst = rman_get_bustag(sc->res[0]);
sc->bsh = rman_get_bushandle(sc->res[0]);
WRITE4(sc, SPI_SRR, SRR_RESET);
DELAY(1000);
reg = (CR_MASTER | CR_MSS | CR_RST_RX | CR_RST_TX);
WRITE4(sc, SPI_CR, reg);
WRITE4(sc, SPI_DGIER, 0);
reg = (CR_MASTER | CR_MSS | CR_SPE);
WRITE4(sc, SPI_CR, reg);
device_add_child(dev, "spibus", DEVICE_UNIT_ANY);
bus_attach_children(dev);
return (0);
}
static int
spi_txrx(struct spi_softc *sc, uint8_t *out_buf,
uint8_t *in_buf, int bufsz, int cs)
{
uint32_t data;
uint32_t i;
for (i = 0; i < bufsz; i++) {
WRITE4(sc, SPI_DTR, out_buf[i]);
while(!(READ4(sc, SPI_SR) & SR_TX_EMPTY))
continue;
data = READ4(sc, SPI_DRR);
if (in_buf)
in_buf[i] = (data & 0xff);
}
return (0);
}
static int
spi_transfer(device_t dev, device_t child, struct spi_command *cmd)
{
struct spi_softc *sc;
uint32_t reg;
uint32_t cs;
sc = device_get_softc(dev);
KASSERT(cmd->tx_cmd_sz == cmd->rx_cmd_sz,
("%s: TX/RX command sizes should be equal", __func__));
KASSERT(cmd->tx_data_sz == cmd->rx_data_sz,
("%s: TX/RX data sizes should be equal", __func__));
spibus_get_cs(child, &cs);
cs &= ~SPIBUS_CS_HIGH;
reg = READ4(sc, SPI_SSR);
reg &= ~(1 << cs);
WRITE4(sc, SPI_SSR, reg);
spi_txrx(sc, cmd->tx_cmd, cmd->rx_cmd, cmd->tx_cmd_sz, cs);
spi_txrx(sc, cmd->tx_data, cmd->rx_data, cmd->tx_data_sz, cs);
reg = READ4(sc, SPI_SSR);
reg |= (1 << cs);
WRITE4(sc, SPI_SSR, reg);
return (0);
}
static phandle_t
axispi_get_node(device_t bus, device_t dev)
{
return (ofw_bus_get_node(bus));
}
static device_method_t spi_methods[] = {
DEVMETHOD(device_probe, spi_probe),
DEVMETHOD(device_attach, spi_attach),
DEVMETHOD(ofw_bus_get_node, axispi_get_node),
DEVMETHOD(spibus_transfer, spi_transfer),
DEVMETHOD_END
};
static driver_t axispi_driver = {
"axispi",
spi_methods,
sizeof(struct spi_softc),
};
DRIVER_MODULE(axispi, simplebus, axispi_driver, 0, 0);
DRIVER_MODULE(ofw_spibus, axispi, ofw_spibus_driver, 0, 0);
MODULE_DEPEND(axispi, ofw_spibus, 1, 1, 1);
SIMPLEBUS_PNP_INFO(compat_data);