aboutsummaryrefslogtreecommitdiffstats
path: root/sources/xdma_driver/xdma/cdev_xvc.c
diff options
context:
space:
mode:
authorAtaberk <olgunataberk@gmail.com>2022-09-25 17:22:03 +0200
committerAtaberk <olgunataberk@gmail.com>2022-09-25 17:22:03 +0200
commitdc0b3db1b4f1895a07e5fe280ee3790e87f97b9f (patch)
treeb47203aa281bdd959def4451c84d310cd9cf2e12 /sources/xdma_driver/xdma/cdev_xvc.c
downloaddram-bender-dc0b3db1b4f1895a07e5fe280ee3790e87f97b9f.tar.gz
Initial commit
Diffstat (limited to 'sources/xdma_driver/xdma/cdev_xvc.c')
-rw-r--r--sources/xdma_driver/xdma/cdev_xvc.c238
1 files changed, 238 insertions, 0 deletions
diff --git a/sources/xdma_driver/xdma/cdev_xvc.c b/sources/xdma_driver/xdma/cdev_xvc.c
new file mode 100644
index 0000000..a077bb1
--- /dev/null
+++ b/sources/xdma_driver/xdma/cdev_xvc.c
@@ -0,0 +1,238 @@
+/*
+ * This file is part of the Xilinx DMA IP Core driver for Linux
+ *
+ * Copyright (c) 2016-present, Xilinx, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
+
+#include "xdma_cdev.h"
+#include "cdev_xvc.h"
+
+#define COMPLETION_LOOP_MAX 100
+
+#define XVC_BAR_LENGTH_REG 0x0
+#define XVC_BAR_TMS_REG 0x4
+#define XVC_BAR_TDI_REG 0x8
+#define XVC_BAR_TDO_REG 0xC
+#define XVC_BAR_CTRL_REG 0x10
+
+#ifdef __REG_DEBUG__
+/* SECTION: Function definitions */
+inline void __write_register(const char *fn, u32 value, void *base,
+ unsigned int off)
+{
+ pr_info("%s: 0x%p, W reg 0x%lx, 0x%x.\n", fn, base, off, value);
+ iowrite32(value, base + off);
+}
+
+inline u32 __read_register(const char *fn, void *base, unsigned int off)
+{
+ u32 v = ioread32(base + off);
+
+ pr_info("%s: 0x%p, R reg 0x%lx, 0x%x.\n", fn, base, off, v);
+ return v;
+}
+#define write_register(v,base,off) __write_register(__func__, v, base, off)
+#define read_register(base,off) __read_register(__func__, base, off)
+
+#else
+#define write_register(v,base,off) iowrite32(v, (base) + (off))
+#define read_register(base,off) ioread32((base) + (off))
+#endif /* #ifdef __REG_DEBUG__ */
+
+
+static int xvc_shift_bits(void *base, u32 tms_bits, u32 tdi_bits,
+ u32 *tdo_bits)
+{
+ u32 control;
+ int count;
+
+ /* set tms bit */
+ write_register(tms_bits, base, XVC_BAR_TMS_REG);
+ /* set tdi bits and shift data out */
+ write_register(tdi_bits, base, XVC_BAR_TDI_REG);
+ /* enable shift operation */
+ write_register(0x1, base, XVC_BAR_CTRL_REG);
+
+ /* poll for completion */
+ count = COMPLETION_LOOP_MAX;
+ while (count) {
+ /* read control reg to check shift operation completion */
+ control = read_register(base, XVC_BAR_CTRL_REG);
+ if ((control & 0x01) == 0)
+ break;
+
+ count--;
+ }
+
+ if (!count) {
+ pr_warn("XVC bar transaction timed out (0x%0X)\n", control);
+ return -ETIMEDOUT;
+ }
+
+ /* read tdo bits back out */
+ *tdo_bits = read_register(base, XVC_BAR_TDO_REG);
+
+ return 0;
+}
+
+static long xvc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ struct xdma_cdev *xcdev = (struct xdma_cdev *)filp->private_data;
+ struct xdma_dev *xdev;
+ struct xvc_ioc xvc_obj;
+ unsigned int opcode;
+ unsigned int total_bits;
+ unsigned int total_bytes;
+ unsigned char *buffer = NULL;
+ unsigned char *tms_buf = NULL;
+ unsigned char *tdi_buf = NULL;
+ unsigned char *tdo_buf = NULL;
+ unsigned int bits, bits_left;
+ void __iomem *iobase;
+ int rv;
+
+ rv = xcdev_check(__func__, xcdev, 0);
+ if (rv < 0)
+ return rv;
+ xdev = xcdev->xdev;
+
+ if (cmd != XDMA_IOCXVC) {
+ pr_info("ioctl 0x%x, UNKNOWN cmd.\n", cmd);
+ return -ENOIOCTLCMD;
+ }
+
+ rv = copy_from_user((void *)&xvc_obj, (void __user *)arg,
+ sizeof(struct xvc_ioc));
+ /* anything not copied ? */
+ if (rv) {
+ pr_info("copy_from_user xvc_obj failed: %d.\n", rv);
+ goto cleanup;
+ }
+
+ opcode = xvc_obj.opcode;
+
+ /* Invalid operation type, no operation performed */
+ if (opcode != 0x01 && opcode != 0x02) {
+ pr_info("UNKNOWN opcode 0x%x.\n", opcode);
+ return -EINVAL;
+ }
+
+ total_bits = xvc_obj.length;
+ total_bytes = (total_bits + 7) >> 3;
+
+ buffer = (char *)kmalloc(total_bytes * 3, GFP_KERNEL);
+ if (!buffer) {
+ pr_info("OOM %u, op 0x%x, len %u bits, %u bytes.\n",
+ 3 * total_bytes, opcode, total_bits, total_bytes);
+ rv = -ENOMEM;
+ goto cleanup;
+ }
+ tms_buf = buffer;
+ tdi_buf = tms_buf + total_bytes;
+ tdo_buf = tdi_buf + total_bytes;
+
+ rv = copy_from_user((void *)tms_buf, xvc_obj.tms_buf, total_bytes);
+ if (rv) {
+ pr_info("copy tmfs_buf failed: %d/%u.\n", rv, total_bytes);
+ goto cleanup;
+ }
+ rv = copy_from_user((void *)tdi_buf, xvc_obj.tdi_buf, total_bytes);
+ if (rv) {
+ pr_info("copy tdi_buf failed: %d/%u.\n", rv, total_bytes);
+ goto cleanup;
+ }
+
+ /* exclusive access */
+ spin_lock(&xcdev->lock);
+
+ iobase = xdev->bar[xcdev->bar] + xcdev->base;
+ /* set length register to 32 initially if more than one
+ * word-transaction is to be done */
+ if (total_bits >= 32)
+ write_register(0x20, iobase, XVC_BAR_LENGTH_REG);
+
+ for (bits = 0, bits_left = total_bits; bits < total_bits; bits += 32,
+ bits_left -= 32) {
+ unsigned int bytes = bits >> 3;
+ unsigned int shift_bytes = 4;
+ u32 tms_store = 0;
+ u32 tdi_store = 0;
+ u32 tdo_store = 0;
+
+ if (bits_left < 32) {
+ /* set number of bits to shift out */
+ write_register(bits_left, iobase, XVC_BAR_LENGTH_REG);
+ shift_bytes = (bits_left + 7) >> 3;
+ }
+
+ memcpy(&tms_store, tms_buf + bytes, shift_bytes);
+ memcpy(&tdi_store, tdi_buf + bytes, shift_bytes);
+
+ /* Shift data out and copy to output buffer */
+ rv = xvc_shift_bits(iobase, tms_store, tdi_store, &tdo_store);
+ if (rv < 0)
+ goto cleanup;
+
+ memcpy(tdo_buf + bytes, &tdo_store, shift_bytes);
+ }
+
+ /* if testing bar access swap tdi and tdo bufferes to "loopback" */
+ if (opcode == 0x2) {
+ char *tmp = tdo_buf;
+
+ tdo_buf = tdi_buf;
+ tdi_buf = tmp;
+ }
+
+ rv = copy_to_user((void *)xvc_obj.tdo_buf, tdo_buf, total_bytes);
+ if (rv) {
+ pr_info("copy back tdo_buf failed: %d/%u.\n", rv, total_bytes);
+ rv = -EFAULT;
+ goto cleanup;
+ }
+
+cleanup:
+ if (buffer)
+ kfree(buffer);
+
+ #if LINUX_VERSION_CODE < KERNEL_VERSION(5,3,0)
+ mmiowb();
+ #endif
+
+ spin_unlock(&xcdev->lock);
+
+ return rv;
+}
+
+/*
+ * character device file operations for the XVC
+ */
+static const struct file_operations xvc_fops = {
+ .owner = THIS_MODULE,
+ .open = char_open,
+ .release = char_close,
+ .unlocked_ioctl = xvc_ioctl,
+};
+
+void cdev_xvc_init(struct xdma_cdev *xcdev)
+{
+#ifdef __XVC_BAR_NUM__
+ xcdev->bar = __XVC_BAR_NUM__;
+#endif
+#ifdef __XVC_BAR_OFFSET__
+ xcdev->base = __XVC_BAR_OFFSET__;
+#else
+ xcdev->base = XVC_BAR_OFFSET_DFLT;
+#endif
+ pr_info("xcdev 0x%p, bar %u, offset 0x%lx.\n",
+ xcdev, xcdev->bar, xcdev->base);
+ cdev_init(&xcdev->cdev, &xvc_fops);
+}