#include <sys/param.h>
#include <sys/types.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/errno.h>
#include <sys/kmem.h>
#include <sys/systm.h>
#include <dev/fdt/fdtvar.h>
#include <arm/imx/imx23_apbdma.h>
#include <arm/imx/imx23_apbdmareg.h>
#include <arm/imx/imx23_apbhdmareg.h>
#include <arm/imx/imx23_apbxdmareg.h>
#include <arm/imx/imx23var.h>
#define APBDMA_CMD_XFER_COUNT __BITS(31, 16)
#define APBDMA_CMD_CMDPIOWORDS __BITS(15, 12)
#define APBDMA_CMD_RESERVED __BITS(11, 9)
#define APBDMA_CMD_HALTONTERMINATE __BIT(8)
#define APBDMA_CMD_WAIT4ENDCMD __BIT(7)
#define APBDMA_CMD_SEMAPHORE __BIT(6)
#define APBDMA_CMD_NANDWAIT4READY __BIT(5)
#define APBDMA_CMD_NANDLOCK __BIT(4)
#define APBDMA_CMD_IRQONCMPLT __BIT(3)
#define APBDMA_CMD_CHAIN __BIT(2)
#define APBDMA_CMD_COMMAND __BITS(1, 0)
#define APBDMA_CMD_XFER_MAX_BYTES 65535
#define APBDMA_CMD_NO_DMA_XFER 0
#define APBDMA_CMD_DMA_WRITE 1
#define APBDMA_CMD_DMA_READ 2
#define APBDMA_CMD_DMA_SENSE 3
#define F_APBH_DMA __BIT(0)
#define F_APBX_DMA __BIT(1)
#define AHB_MAX_DMA_CHANNELS 16
#define DMA_IRQ_CMDCMPLT 0
#define DMA_IRQ_TERM 1
#define DMA_IRQ_BUS_ERROR 2
#define MAX_PIO_WORDS 3
#define IMX23_DMA_MAX_SEGMENTS 16
#define HW_APB_CHN_NXTCMDAR(base, channel) (base + (0x70 * channel))
#define HW_APB_CHN_SEMA(base, channel) (base + (0x70 * channel))
#define DMA_RD(sc, reg) \
bus_space_read_4(sc->sc_iot, sc->sc_ioh, (reg))
#define DMA_WR(sc, reg, val) \
bus_space_write_4(sc->sc_iot, sc->sc_ioh, (reg), (val))
#define APBDMA_SOFT_RST_LOOP 455
struct imx23_apbdma_fdt_config {
u_int flags;
u_int num_channels;
};
struct imx23_apbdma_command {
void *next;
uint32_t control;
void *buffer;
uint32_t pio_words[MAX_PIO_WORDS];
};
struct imx23_apbdma_softc;
struct imx23_apbdma_channel {
u_int chan_index;
void (*chan_cb)(void *);
void *chan_cbarg;
struct imx23_apbdma_softc *chan_parent;
bus_dmamap_t chan_dmamp;
bus_dma_segment_t chan_ds[1];
bus_size_t chan_cmd_sz;
struct imx23_apbdma_command *chan_cmds;
};
struct imx23_apbdma_softc {
device_t sc_dev;
bus_dma_tag_t sc_dmat;
bus_space_handle_t sc_ioh;
bus_space_tag_t sc_iot;
u_int flags;
struct imx23_apbdma_channel sc_chan[AHB_MAX_DMA_CHANNELS];
};
static int imx23_apbdma_match(device_t, cfdata_t, void *);
static void imx23_apbdma_attach(device_t, device_t, void *);
static void imx23_apbdma_reset(struct imx23_apbdma_softc *);
static void imx23_apbdma_init(struct imx23_apbdma_softc *);
static void * imx23_apbdma_fdt_acquire(device_t, const void *, size_t,
void (*)(void *), void *);
static void imx23_apbdma_fdt_release(device_t, void *);
static int imx23_apbdma_fdt_transfer(device_t, void *,
struct fdtbus_dma_req *);
static void imx23_apbdma_fdt_halt(device_t, void *);
static int imx23_apbdma_intr(void *);
static int imx23_apbdma_pio_transfer(struct imx23_apbdma_softc *sc,
struct imx23_apbdma_channel *chan,
struct fdtbus_dma_req *req);
static int imx23_apbdma_data_transfer(struct imx23_apbdma_softc *sc,
struct imx23_apbdma_channel *chan,
struct fdtbus_dma_req *req);
CFATTACH_DECL_NEW(imx23apbdma, sizeof(struct imx23_apbdma_softc),
imx23_apbdma_match, imx23_apbdma_attach, NULL, NULL);
static const struct fdtbus_dma_controller_func imx23_apbdma_fdt_dma_funcs = {
.acquire = imx23_apbdma_fdt_acquire,
.release = imx23_apbdma_fdt_release,
.transfer = imx23_apbdma_fdt_transfer,
.halt = imx23_apbdma_fdt_halt,
};
static const struct imx23_apbdma_fdt_config imx23_apbh_config = {
.flags = F_APBH_DMA,
.num_channels = 8,
};
static const struct imx23_apbdma_fdt_config imx23_apbx_config = {
.flags = F_APBX_DMA,
.num_channels = 16,
};
static const struct device_compatible_entry compat_data[] = {
{ .compat = "fsl,imx23-dma-apbh", .data = &imx23_apbh_config },
{ .compat = "fsl,imx23-dma-apbx", .data = &imx23_apbx_config },
DEVICE_COMPAT_EOL
};
static void *
imx23_apbdma_fdt_acquire(device_t dev, const void *data, size_t len,
void (*cb)(void *), void *cbarg)
{
struct imx23_apbdma_softc *sc = device_private(dev);
struct imx23_apbdma_channel *chan;
const uint32_t *specifier = data;
int rsegs;
if (len != 4) {
return NULL;
}
const u_int chan_index = be32toh(specifier[0]);
chan = &sc->sc_chan[chan_index];
chan->chan_cb = cb;
chan->chan_cbarg = cbarg;
DMA_WR(sc, HW_APB_CTRL1_SET, (1<<chan->chan_index)<<16);
chan->chan_cmd_sz = IMX23_DMA_MAX_SEGMENTS *
sizeof(struct imx23_apbdma_command);
if (bus_dmamem_alloc(sc->sc_dmat, chan->chan_cmd_sz, PAGE_SIZE, 0,
chan->chan_ds, 1, &rsegs, BUS_DMA_WAITOK)) {
return NULL;
}
if (bus_dmamem_map(sc->sc_dmat, chan->chan_ds, rsegs, chan->chan_cmd_sz,
(void **)&chan->chan_cmds, BUS_DMA_WAITOK)) {
return NULL;
}
if (bus_dmamap_create(sc->sc_dmat, MAXPHYS, 1, MAXPHYS, 0,
BUS_DMA_WAITOK, &chan->chan_dmamp)) {
return NULL;
}
if (bus_dmamap_load(sc->sc_dmat, chan->chan_dmamp, chan->chan_cmds,
chan->chan_cmd_sz, NULL, BUS_DMA_WAITOK)) {
return NULL;
}
struct imx23_apbdma_command *pa_cmd = (void *) chan->chan_ds[0].ds_addr;
for (int i = 0; i < IMX23_DMA_MAX_SEGMENTS - 1; i++) {
chan->chan_cmds[i].next = &pa_cmd[i + 1].next;
}
return chan;
}
static void
imx23_apbdma_fdt_release(device_t dev, void *priv)
{
struct imx23_apbdma_softc *sc = device_private(dev);
struct imx23_apbdma_channel *chan = priv;
bus_dmamap_unload(sc->sc_dmat, chan->chan_dmamp);
bus_dmamap_destroy(sc->sc_dmat, chan->chan_dmamp);
bus_dmamem_unmap(sc->sc_dmat, chan->chan_cmds, chan->chan_cmd_sz);
bus_dmamem_free(sc->sc_dmat, chan->chan_ds, 1);
}
static int imx23_apbdma_pio_transfer(struct imx23_apbdma_softc *sc,
struct imx23_apbdma_channel *chan,
struct fdtbus_dma_req *req)
{
if (req->dreq_nsegs != 0) {
return EINVAL;
}
if (req->dreq_datalen > MAX_PIO_WORDS) {
return EINVAL;
}
chan->chan_cmds[0].control = __SHIFTIN(0, APBDMA_CMD_XFER_COUNT) |
__SHIFTIN(req->dreq_datalen, APBDMA_CMD_CMDPIOWORDS) |
__SHIFTIN(APBDMA_CMD_NO_DMA_XFER, APBDMA_CMD_COMMAND) |
APBDMA_CMD_HALTONTERMINATE | APBDMA_CMD_SEMAPHORE |
APBDMA_CMD_IRQONCMPLT;
if (req->dreq_block_irq) {
chan->chan_cmds[0].control |= APBDMA_CMD_WAIT4ENDCMD;
}
uint32_t *pio_words = req->dreq_data;
for (int i = 0; i < req->dreq_datalen; i++) {
chan->chan_cmds[0].pio_words[i] = pio_words[i];
}
return 0;
}
static int imx23_apbdma_data_transfer(struct imx23_apbdma_softc *sc,
struct imx23_apbdma_channel *chan,
struct fdtbus_dma_req *req)
{
if (req->dreq_nsegs < 0 || req->dreq_nsegs > IMX23_DMA_MAX_SEGMENTS) {
return EINVAL;
}
for (int i = 0; i < req->dreq_nsegs; i++) {
if (req->dreq_segs[i].ds_len > APBDMA_CMD_XFER_MAX_BYTES) {
return EINVAL;
}
}
if (req->dreq_datalen > MAX_PIO_WORDS) {
return EINVAL;
}
uint32_t dma_dir;
if (req->dreq_dir == FDT_DMA_WRITE) {
dma_dir = __SHIFTIN(APBDMA_CMD_DMA_READ, APBDMA_CMD_COMMAND);
} else {
dma_dir = __SHIFTIN(APBDMA_CMD_DMA_WRITE, APBDMA_CMD_COMMAND);
}
for (int i = 0; i < req->dreq_nsegs; i++) {
chan->chan_cmds[i].buffer = (void *)req->dreq_segs[i].ds_addr;
chan->chan_cmds[i].control =
__SHIFTIN(req->dreq_segs[i].ds_len, APBDMA_CMD_XFER_COUNT) |
__SHIFTIN(0, APBDMA_CMD_CMDPIOWORDS) | dma_dir |
APBDMA_CMD_HALTONTERMINATE | APBDMA_CMD_CHAIN;
}
uint32_t *pio_words = req->dreq_data;
for (int i = 0; i < req->dreq_datalen; i++) {
chan->chan_cmds[0].pio_words[i] = pio_words[i];
}
chan->chan_cmds[0].control &= ~APBDMA_CMD_CMDPIOWORDS;
chan->chan_cmds[0].control |=
__SHIFTIN(req->dreq_datalen, APBDMA_CMD_CMDPIOWORDS);
int tail_index = req->dreq_nsegs - 1;
chan->chan_cmds[tail_index].control &= ~APBDMA_CMD_CHAIN;
chan->chan_cmds[tail_index].control |=
APBDMA_CMD_SEMAPHORE | APBDMA_CMD_IRQONCMPLT;
if (req->dreq_block_irq) {
chan->chan_cmds[tail_index].control |= APBDMA_CMD_WAIT4ENDCMD;
}
return 0;
}
static int
imx23_apbdma_fdt_transfer(device_t dev, void *priv, struct fdtbus_dma_req *req)
{
struct imx23_apbdma_softc *sc = device_private(dev);
struct imx23_apbdma_channel *chan = priv;
int error = 0;
uint32_t reg;
uint8_t val;
if (req->dreq_dir == FDT_DMA_NO_XFER) {
error = imx23_apbdma_pio_transfer(sc, chan, req);
if (error)
return error;
} else {
error = imx23_apbdma_data_transfer(sc, chan, req);
if (error)
return error;
}
bus_dmamap_sync(sc->sc_dmat, chan->chan_dmamp, 0, chan->chan_cmd_sz,
BUS_DMASYNC_PREWRITE);
if (sc->flags & F_APBH_DMA)
reg =
HW_APB_CHN_NXTCMDAR(HW_APBH_CH0_NXTCMDAR, chan->chan_index);
else
reg =
HW_APB_CHN_NXTCMDAR(HW_APBX_CH0_NXTCMDAR, chan->chan_index);
DMA_WR(sc, reg, chan->chan_ds[0].ds_addr);
if (sc->flags & F_APBH_DMA) {
reg = HW_APB_CHN_SEMA(HW_APBH_CH0_SEMA, chan->chan_index);
val = __SHIFTIN(1, HW_APBH_CH0_SEMA_INCREMENT_SEMA);
} else {
reg = HW_APB_CHN_SEMA(HW_APBX_CH0_SEMA, chan->chan_index);
val = __SHIFTIN(1, HW_APBX_CH0_SEMA_INCREMENT_SEMA);
}
DMA_WR(sc, reg, val);
return 0;
}
static void
imx23_apbdma_fdt_halt(device_t dev, void *priv)
{
}
static int
imx23_apbdma_intr(void *frame)
{
struct imx23_apbdma_channel *chan = frame;
struct imx23_apbdma_softc *sc = chan->chan_parent;
unsigned int reason = 0;
if (DMA_RD(sc, HW_APB_CTRL1) & (1<<chan->chan_index))
reason = DMA_IRQ_CMDCMPLT;
if (DMA_RD(sc, HW_APB_CTRL2) & (1<<chan->chan_index)) {
if (DMA_RD(sc, HW_APB_CTRL2) & (1<<chan->chan_index)<<16)
reason = DMA_IRQ_BUS_ERROR;
else
reason = DMA_IRQ_TERM;
}
if (reason) {
DMA_WR(sc, HW_APB_CTRL2_CLR, (1<<chan->chan_index));
} else {
if (sc->flags & F_APBH_DMA) {
DMA_WR(sc, HW_APB_CTRL1_CLR, (1<<chan->chan_index));
} else {
DMA_WR(sc, HW_APB_CTRL1_CLR, (1<<chan->chan_index));
}
}
bus_dmamap_sync(sc->sc_dmat, chan->chan_dmamp, 0, chan->chan_cmd_sz,
BUS_DMASYNC_POSTWRITE);
if (chan->chan_cb != NULL) {
chan->chan_cb(chan->chan_cbarg);
}
if (reason == DMA_IRQ_TERM) {
if (sc->flags & F_APBH_DMA) {
DMA_WR(sc, HW_APB_CTRL0_SET,
__SHIFTIN((1 << chan->chan_index),
HW_APBH_CTRL0_RESET_CHANNEL));
while (DMA_RD(sc, HW_APB_CTRL0) & HW_APBH_CTRL0_RESET_CHANNEL)
continue;
} else {
DMA_WR(sc, HW_APBX_CHANNEL_CTRL_SET,
__SHIFTIN((1 << chan->chan_index),
HW_APBH_CTRL0_RESET_CHANNEL));
while (DMA_RD(sc, HW_APBX_CHANNEL_CTRL) & (1 << chan->chan_index))
continue;
}
}
return 1;
}
static int
imx23_apbdma_match(device_t parent, cfdata_t match, void *aux)
{
struct fdt_attach_args *const faa = aux;
return of_compatible_match(faa->faa_phandle, compat_data);
}
static void
imx23_apbdma_attach(device_t parent, device_t self, void *aux)
{
struct imx23_apbdma_softc *const sc = device_private(self);
struct fdt_attach_args *const faa = aux;
const int phandle = faa->faa_phandle;
const struct imx23_apbdma_fdt_config *config;
int len;
char intrstr[128];
config = of_compatible_lookup(phandle, compat_data)->data;
sc->flags = config->flags;
sc->sc_dev = self;
sc->sc_iot = faa->faa_bst;
sc->sc_dmat = faa->faa_dmat;
bus_addr_t addr;
bus_size_t size;
if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
aprint_error(": couldn't get register address\n");
return;
}
if (bus_space_map(faa->faa_bst, addr, size, 0, &sc->sc_ioh)) {
aprint_error(": couldn't map registers\n");
return;
}
const u_int *specifier = fdtbus_get_prop(phandle, "interrupts", &len);
KASSERT(len == sizeof(u_int)*config->num_channels);
for (int i = 0; i < config->num_channels; i++) {
u_int irq = be32toh(specifier[i]);
if (irq == 0)
continue;
bool skip = false;
for (int j = 0; j<i;j++) {
if (be32toh(specifier[j]) == irq) {
skip = true;
break;
}
}
if (skip) {
continue;
}
if (!fdtbus_intr_str(phandle, i, intrstr, sizeof(intrstr))) {
aprint_error(": failed to decode interrupt\n");
return;
}
void *ih = fdtbus_intr_establish_xname(
phandle, i, IPL_SCHED, FDT_INTR_MPSAFE, imx23_apbdma_intr,
&sc->sc_chan[i], device_xname(self));
if (ih == NULL) {
aprint_error_dev(
self, ": couldn't install interrupt handler %s\n",
intrstr);
return;
}
}
for (int i = 0; i < AHB_MAX_DMA_CHANNELS; i++) {
sc->sc_chan[i].chan_index = i;
sc->sc_chan[i].chan_parent = sc;
}
imx23_apbdma_reset(sc);
imx23_apbdma_init(sc);
fdtbus_register_dma_controller(self, phandle,
&imx23_apbdma_fdt_dma_funcs);
if (sc->flags & F_APBH_DMA) {
aprint_normal(": type=apbh\n");
} else if (sc->flags & F_APBX_DMA) {
aprint_normal(": type=apbx\n");
}
}
void
imx23_apbdma_reset(struct imx23_apbdma_softc *sc)
{
unsigned int loop;
DMA_WR(sc, HW_APB_CTRL0_CLR, HW_APB_CTRL0_SFTRST);
loop = 0;
while ((DMA_RD(sc, HW_APB_CTRL0) & HW_APB_CTRL0_SFTRST) ||
(loop < APBDMA_SOFT_RST_LOOP))
loop++;
DMA_WR(sc, HW_APB_CTRL0_CLR, HW_APB_CTRL0_CLKGATE);
DMA_WR(sc, HW_APB_CTRL0_SET, HW_APB_CTRL0_SFTRST);
while (!(DMA_RD(sc, HW_APB_CTRL0) & HW_APB_CTRL0_CLKGATE))
continue;
DMA_WR(sc, HW_APB_CTRL0_CLR, HW_APB_CTRL0_SFTRST);
loop = 0;
while ((DMA_RD(sc, HW_APB_CTRL0) & HW_APB_CTRL0_SFTRST) ||
(loop < APBDMA_SOFT_RST_LOOP))
loop++;
DMA_WR(sc, HW_APB_CTRL0_CLR, HW_APB_CTRL0_CLKGATE);
while (DMA_RD(sc, HW_APB_CTRL0) & HW_APB_CTRL0_CLKGATE)
continue;
}
static void
imx23_apbdma_init(struct imx23_apbdma_softc *sc)
{
if (sc->flags & F_APBH_DMA) {
DMA_WR(sc, HW_APBH_CTRL0_SET, HW_APBH_CTRL0_AHB_BURST8_EN);
DMA_WR(sc, HW_APBH_CTRL0_SET, HW_APBH_CTRL0_APB_BURST4_EN);
}
}