| // 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), |
| }; |