blob: 0290b93340f4d6e10a2f2c5fb0b754972c3f067b [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Direct Memory Access U-Class Simulation driver
*
* Copyright (C) 2018 Texas Instruments Incorporated <www.ti.com>
*
* Author: Grygorii Strashko <grygorii.strashko@ti.com>
*/
#include <dm.h>
#include <log.h>
#include <malloc.h>
#include <dm/read.h>
#include <dma-uclass.h>
#include <dt-structs.h>
#include <errno.h>
#include <linux/printk.h>
#define SANDBOX_DMA_CH_CNT 3
#define SANDBOX_DMA_BUF_SIZE 1024
struct sandbox_dma_chan {
struct sandbox_dma_dev *ud;
char name[20];
u32 id;
enum dma_direction dir;
bool in_use;
bool enabled;
};
struct sandbox_dma_dev {
struct device *dev;
u32 ch_count;
struct sandbox_dma_chan channels[SANDBOX_DMA_CH_CNT];
uchar buf[SANDBOX_DMA_BUF_SIZE];
uchar *buf_rx;
size_t data_len;
u32 meta;
};
static int sandbox_dma_transfer(struct udevice *dev, int direction,
dma_addr_t dst, dma_addr_t src, size_t len)
{
memcpy((void *)dst, (void *)src, len);
return 0;
}
static int sandbox_dma_of_xlate(struct dma *dma,
struct ofnode_phandle_args *args)
{
struct sandbox_dma_dev *ud = dev_get_priv(dma->dev);
struct sandbox_dma_chan *uc;
debug("%s(dma id=%u)\n", __func__, args->args[0]);
if (args->args[0] >= SANDBOX_DMA_CH_CNT)
return -EINVAL;
dma->id = args->args[0];
uc = &ud->channels[dma->id];
if (dma->id == 1)
uc->dir = DMA_MEM_TO_DEV;
else if (dma->id == 2)
uc->dir = DMA_DEV_TO_MEM;
else
uc->dir = DMA_MEM_TO_MEM;
debug("%s(dma id=%lu dir=%d)\n", __func__, dma->id, uc->dir);
return 0;
}
static int sandbox_dma_request(struct dma *dma)
{
struct sandbox_dma_dev *ud = dev_get_priv(dma->dev);
struct sandbox_dma_chan *uc;
if (dma->id >= SANDBOX_DMA_CH_CNT)
return -EINVAL;
uc = &ud->channels[dma->id];
if (uc->in_use)
return -EBUSY;
uc->in_use = true;
debug("%s(dma id=%lu in_use=%d)\n", __func__, dma->id, uc->in_use);
return 0;
}
static int sandbox_dma_rfree(struct dma *dma)
{
struct sandbox_dma_dev *ud = dev_get_priv(dma->dev);
struct sandbox_dma_chan *uc;
if (dma->id >= SANDBOX_DMA_CH_CNT)
return -EINVAL;
uc = &ud->channels[dma->id];
if (!uc->in_use)
return -EINVAL;
uc->in_use = false;
ud->buf_rx = NULL;
ud->data_len = 0;
debug("%s(dma id=%lu in_use=%d)\n", __func__, dma->id, uc->in_use);
return 0;
}
static int sandbox_dma_enable(struct dma *dma)
{
struct sandbox_dma_dev *ud = dev_get_priv(dma->dev);
struct sandbox_dma_chan *uc;
if (dma->id >= SANDBOX_DMA_CH_CNT)
return -EINVAL;
uc = &ud->channels[dma->id];
if (!uc->in_use)
return -EINVAL;
if (uc->enabled)
return -EINVAL;
uc->enabled = true;
debug("%s(dma id=%lu enabled=%d)\n", __func__, dma->id, uc->enabled);
return 0;
}
static int sandbox_dma_disable(struct dma *dma)
{
struct sandbox_dma_dev *ud = dev_get_priv(dma->dev);
struct sandbox_dma_chan *uc;
if (dma->id >= SANDBOX_DMA_CH_CNT)
return -EINVAL;
uc = &ud->channels[dma->id];
if (!uc->in_use)
return -EINVAL;
if (!uc->enabled)
return -EINVAL;
uc->enabled = false;
debug("%s(dma id=%lu enabled=%d)\n", __func__, dma->id, uc->enabled);
return 0;
}
static int sandbox_dma_send(struct dma *dma,
void *src, size_t len, void *metadata)
{
struct sandbox_dma_dev *ud = dev_get_priv(dma->dev);
struct sandbox_dma_chan *uc;
if (dma->id >= SANDBOX_DMA_CH_CNT)
return -EINVAL;
if (!src || !metadata)
return -EINVAL;
debug("%s(dma id=%lu)\n", __func__, dma->id);
uc = &ud->channels[dma->id];
if (uc->dir != DMA_MEM_TO_DEV)
return -EINVAL;
if (!uc->in_use)
return -EINVAL;
if (!uc->enabled)
return -EINVAL;
if (len >= SANDBOX_DMA_BUF_SIZE)
return -EINVAL;
memcpy(ud->buf, src, len);
ud->data_len = len;
ud->meta = *((u32 *)metadata);
debug("%s(dma id=%lu len=%zu meta=%08x)\n",
__func__, dma->id, len, ud->meta);
return 0;
}
static int sandbox_dma_receive(struct dma *dma, void **dst, void *metadata)
{
struct sandbox_dma_dev *ud = dev_get_priv(dma->dev);
struct sandbox_dma_chan *uc;
if (dma->id >= SANDBOX_DMA_CH_CNT)
return -EINVAL;
if (!dst || !metadata)
return -EINVAL;
uc = &ud->channels[dma->id];
if (uc->dir != DMA_DEV_TO_MEM)
return -EINVAL;
if (!uc->in_use)
return -EINVAL;
if (!uc->enabled)
return -EINVAL;
if (!ud->data_len)
return 0;
if (ud->buf_rx) {
memcpy(ud->buf_rx, ud->buf, ud->data_len);
*dst = ud->buf_rx;
} else {
memcpy(*dst, ud->buf, ud->data_len);
}
*((u32 *)metadata) = ud->meta;
debug("%s(dma id=%lu len=%zu meta=%08x %p)\n",
__func__, dma->id, ud->data_len, ud->meta, *dst);
return ud->data_len;
}
static int sandbox_dma_prepare_rcv_buf(struct dma *dma, void *dst, size_t size)
{
struct sandbox_dma_dev *ud = dev_get_priv(dma->dev);
ud->buf_rx = dst;
return 0;
}
static const struct dma_ops sandbox_dma_ops = {
.transfer = sandbox_dma_transfer,
.of_xlate = sandbox_dma_of_xlate,
.request = sandbox_dma_request,
.rfree = sandbox_dma_rfree,
.enable = sandbox_dma_enable,
.disable = sandbox_dma_disable,
.send = sandbox_dma_send,
.receive = sandbox_dma_receive,
.prepare_rcv_buf = sandbox_dma_prepare_rcv_buf,
};
static int sandbox_dma_probe(struct udevice *dev)
{
struct dma_dev_priv *uc_priv = dev_get_uclass_priv(dev);
struct sandbox_dma_dev *ud = dev_get_priv(dev);
int i, ret = 0;
uc_priv->supported = DMA_SUPPORTS_MEM_TO_MEM |
DMA_SUPPORTS_MEM_TO_DEV |
DMA_SUPPORTS_DEV_TO_MEM;
ud->ch_count = SANDBOX_DMA_CH_CNT;
ud->buf_rx = NULL;
ud->meta = 0;
ud->data_len = 0;
pr_err("Number of channels: %u\n", ud->ch_count);
for (i = 0; i < ud->ch_count; i++) {
struct sandbox_dma_chan *uc = &ud->channels[i];
uc->ud = ud;
uc->id = i;
sprintf(uc->name, "DMA chan%d\n", i);
uc->in_use = false;
uc->enabled = false;
}
return ret;
}
static const struct udevice_id sandbox_dma_ids[] = {
{ .compatible = "sandbox,dma" },
{ }
};
U_BOOT_DRIVER(sandbox_dma) = {
.name = "sandbox-dma",
.id = UCLASS_DMA,
.of_match = sandbox_dma_ids,
.ops = &sandbox_dma_ops,
.probe = sandbox_dma_probe,
.priv_auto = sizeof(struct sandbox_dma_dev),
};