// created by (aw)
// taking clues from Xilinx ug1165 "blink" example
// carried forward from msdd / sspt project
// 20 Feb 2020
// MPSoC modification 11 Dec 2022
// of and 32/64 bit modification 27 Feb 2023
// auto insnod 11 Aug 2023
// multiple instance support 21 Dec 2023
// ioctl 31 Oct 2024
// kalloc optimization 3 Nov 2024
// multi-command ioctl 23 Feb 2025
// ioctl without user space dereferencing 23 Dec 2025
// result codes 8 Mar 2026

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/spinlock.h>

#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>

#include "dbeaxilite.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("MPSI Technologies GmbH");
MODULE_DESCRIPTION("dbeaxilite - WhizniumDBE AXI4-Lite character device loadable kernel module");

#define DRIVER_NAME "dbeaxilite"

#undef RXTXDUMP

#define NMAX_MINOR 8

#define SIZERWBUF 1024

#define FALSE 0
#define TRUE 1

#define FLS32 0xAAAAAAAA
#define TRU32 0x55555555

#define FLS64 0xAAAAAAAAAAAAAAAA
#define TRU64 0x5555555555555555

#define OFS_DBEAXILITE_SETWRITE 0x0
#define OFS_DBEAXILITE_WRITE 0x10
#define OFS_DBEAXILITE_SETREAD 0x20
#define OFS_DBEAXILITE_READ 0x30

static struct class* deviceFileClass;
static int major_num;

struct dbeaxilite_data_t {
	dev_t dev;
	bool mmapNotBus;
	void __iomem* mm;
	int busyNotIdle;

	struct dbeaxilite_cmd_t cmd;
	struct dbeaxilite_bufxf_t bufxf;

	void* rwbuf;

	spinlock_t sAccess;

#ifdef RXTXDUMP
	char rxtxdump[32];
#endif

	__u16 crc;
};
static struct dbeaxilite_data_t devdatas[NMAX_MINOR];

#define VECDBEVBUFXFOP_xfer 0x02

#define VECDBEVRESULT_ok 0x00
#define VECDBEVRESULT_nok 0x01

#define VECDBEVRESULT_poll_ETX1 0x10
#define VECDBEVRESULT_poll_ERX1 0x11
#define VECDBEVRESULT_poll_ERX1NAK 0x12
#define VECDBEVRESULT_poll_ERX2 0x13
#define VECDBEVRESULT_poll_ERX2NCRC 0x14
#define VECDBEVRESULT_poll_ETX2 0x15

#define VECDBEVRESULT_cmd_ETX1 0x20
#define VECDBEVRESULT_cmd_ERX1 0x21
#define VECDBEVRESULT_cmd_ERX1NAK 0x22
#define VECDBEVRESULT_cmd_ETX2 0x23
#define VECDBEVRESULT_cmd_ERX2 0x24
#define VECDBEVRESULT_cmd_ERX2NAK 0x25
#define VECDBEVRESULT_cmd_ETX3 0x26
#define VECDBEVRESULT_cmd_ERX3 0x27
#define VECDBEVRESULT_cmd_ERX3NAK 0x28
#define VECDBEVRESULT_cmd_ERX4 0x29
#define VECDBEVRESULT_cmd_ERX4NCMD 0x2A
#define VECDBEVRESULT_cmd_ETX4 0x2B
#define VECDBEVRESULT_cmd_ERX5 0x2C
#define VECDBEVRESULT_cmd_ERX5NCRC 0x2D
#define VECDBEVRESULT_cmd_ETX5 0x2E

#define VECDBEVRESULT_cmdmmap_ETX 0x30
#define VECDBEVRESULT_cmdmmap_ERX 0x31

#define VECDBEVRESULT_bufxf_ETX1 0x40
#define VECDBEVRESULT_bufxf_ERX1 0x41
#define VECDBEVRESULT_bufxf_ERX1NAK 0x42
#define VECDBEVRESULT_bufxf_ETX2 0x43
#define VECDBEVRESULT_bufxf_ERX2 0x44
#define VECDBEVRESULT_bufxf_ERX2NAK 0x45
#define VECDBEVRESULT_bufxf_ERX2NCRC 0x46

#define CRCPOLY 0x8005

/***** WhizniumDBE command / buffer transfer functionality *****/

inline static void crc_reset(
			struct dbeaxilite_data_t* devdata
		) {
	devdata->crc = 0;
};

inline static void crc_includeByte(
			struct dbeaxilite_data_t* devdata
			, __u8 b
		) {
	__u16 newcrc;
	int bit;

	__u8 i;

	i = 128;

	while (i != 0) {
		bit = b & i;

		newcrc = (devdata->crc << 1);
		if (bit) newcrc |= 0x0001;

		if (devdata->crc & 0x8000) devdata->crc = newcrc ^ CRCPOLY;
		else devdata->crc = newcrc;

		i >>= 1;
	};
};

inline static void crc_includeBytes(
			struct dbeaxilite_data_t* devdata
			, int kernelNotUser
			, __u8* buf
			, const size_t buflen
		) {
	unsigned int i;
	__u8 b;

	if (!kernelNotUser)
		for (i = 0; i < buflen; i++) {
			if (copy_from_user(&b, (const void __user*) (buf + i), 1) != 0) break;
			crc_includeByte(devdata, b);
		}
	else for (i = 0; i < buflen; i++) crc_includeByte(devdata, buf[i]);
};

inline static void crc_finalize(
			struct dbeaxilite_data_t* devdata
		) {
	crc_includeByte(devdata, 0x00);
	crc_includeByte(devdata, 0x00);
};

inline static void setBuffer(
			__u8* buf
			, const __u8 tixVBuffer
		) {
	// txbuf byte 0
	buf[0] = tixVBuffer;
};

inline static void setController(
			__u8* buf
			, const __u8 tixVController
		) {
	// txbuf byte 1
	buf[1] = tixVController;
};

inline static void setBufxfop(
			__u8* buf
			, const __u8 tixVBufxfop
		) {
	// txbuf byte 2
	buf[2] = tixVBufxfop;
};

inline static void setLength(
			__u8* buf
			, const size_t length
		) {
	// txbuf bytes 3..6
	*((__u32*) &(buf[3])) = cpu_to_be32(length);
};

inline static void setCrc(
			struct dbeaxilite_data_t* devdata
			, int kernelNotUser
			, __u8* buf
			, size_t ofs
		) {
	long res;

	__u16 crc;

	// txbuf bytes 7..8 by default
	if (ofs == 0) ofs = 7;

	crc = cpu_to_be16(devdata->crc);

	if (!kernelNotUser) res = copy_to_user((void __user*) (buf + ofs), (void*) &crc, 2);
	else *((__u16*) &(buf[ofs])) = crc;
};

static ssize_t rx(
			struct dbeaxilite_data_t* devdata
			, int kernelNotUser
			, __u8* buf_ext
			, const size_t reqlen
		) {
	ssize_t retval = 0; // number of bytes read

	__u8* buf;
	__u8* buf_lcl;

	size_t i;
#ifdef RXTXDUMP
	__u8 c;
#endif

#ifdef CONFIG_64BIT
	__u64 rdy;

	size_t len64 = reqlen / 8;
	if ((reqlen%8) > 0) len64++;

	buf = NULL;
	if (!kernelNotUser && ((8*len64) > SIZERWBUF)) {
		buf_lcl = kmalloc(8 * len64, 0);
		buf = buf_lcl;
	} else {
		buf_lcl = NULL;
		if (kernelNotUser) buf = buf_ext;
		else buf = devdata->rwbuf;
	};

	if (buf) {
		for (i = 0; i <= 20; i++) {
			// wait for max. 210us (up to 20)
			if (i != 0) udelay(i);

			iowrite64(FLS64, devdata->mm + OFS_DBEAXILITE_SETREAD);
			rdy = ioread64(devdata->mm + OFS_DBEAXILITE_SETREAD);
		
			if (rdy == FLS64) break;
		};

		if (rdy == FLS64) {
			for (i = 0; i < len64; i++) ((__u64*) buf)[i] = cpu_to_le64(ioread64(devdata->mm + OFS_DBEAXILITE_READ));

			iowrite64(TRU64, devdata->mm + OFS_DBEAXILITE_SETREAD);

			if (kernelNotUser) retval = reqlen;
			else retval = reqlen - copy_to_user((void __user*) buf_ext, buf, reqlen);
		};// else errno = ETIMEDOUT;
	};// else errno = ENOMEM;
#else
	__u32 rdy;

	size_t len32 = reqlen / 4;
	if ((reqlen%4) > 0) len32++;

	buf = NULL;
	if (!kernelNotUser && ((4*len32) > SIZERWBUF)) {
		buf_lcl = kmalloc(4 * len32, 0);
		buf = buf_lcl;
	} else {
		buf_lcl = NULL;
		if (kernelNotUser) buf = buf_ext;
		else buf = devdata->rwbuf;
	};

	if (buf) {
		for (i = 0; i <= 20; i++) {
			// wait for max. 210us
			if (i != 0) udelay(i);

			iowrite32(FLS32, devdata->mm + OFS_DBEAXILITE_SETREAD);
			rdy = ioread32(devdata->mm + OFS_DBEAXILITE_SETREAD);
		
			if (rdy == FLS32) break;
		};

		if (rdy == FLS32) {
			for (i = 0; i < len32; i++) ((__u32*) buf)[i] = cpu_to_le32(ioread32(devdata->mm + OFS_DBEAXILITE_READ));
		
			iowrite32(TRU32, devdata->mm + OFS_DBEAXILITE_SETREAD);

			if (kernelNotUser) retval = reqlen;
			else retval = reqlen - copy_to_user((void __user*) buf_ext, buf, reqlen);
		};// else errno = ETIMEDOUT;
	};// else errno = ENOMEM;
#endif

#ifdef RXTXDUMP
	if (buf) {
		for (i = 0; (i < reqlen) && (i < 15); i++) {
			c = (buf[i] >> 4) + 0x30;
			if (c > 0x39) c += 0x07;
			devdata->rxtxdump[2*i] = (char) c;

			c = (buf[i] & 0x0F) + 0x30;
			if (c > 0x39) c += 0x07;
			devdata->rxtxdump[2*i+1] = (char) c;
		};
		i *= 2;
		if (i == 30) devdata->rxtxdump[i++] = '+';
		devdata->rxtxdump[i] = '\0';
		printk(KERN_INFO "rx %s%s\n", devdata->rxtxdump, (retval == reqlen) ? "" : " fail");
	};
#endif

	if (buf_lcl) kfree(buf_lcl);

	return retval;
};

static ssize_t tx(
			struct dbeaxilite_data_t* devdata
			, int kernelNotUser
			, const __u8* buf_ext
			, const size_t reqlen
		) {
	ssize_t retval = 0; // number of bytes written

	__u8* buf;
	__u8* buf_lcl;

	size_t i;
#ifdef RXTXDUMP
	__u8 c;
#endif

#ifdef CONFIG_64BIT
	__u64 rdy;

	size_t len64 = reqlen / 8;
	if ((reqlen%8) > 0) len64++;

	buf = NULL;
	if (!kernelNotUser && ((8*len64) > SIZERWBUF)) {
		buf_lcl = kmalloc(8 * len64, 0);
		buf = buf_lcl;
	} else {
		buf_lcl = NULL;
		if (kernelNotUser) buf = (__u8*) buf_ext;
		else buf = devdata->rwbuf;
	};

	if (!kernelNotUser && buf) if (copy_from_user(buf, (const void __user*) buf_ext, reqlen) != 0) {
		//errno = EFAULT
		buf = NULL;
	};

	if (buf) {
		for (i = 0; i <= 20; i++) {
			// wait for max. 210us
			if (i != 0) udelay(i);

			iowrite64(FLS64, devdata->mm + OFS_DBEAXILITE_SETWRITE);
			rdy = ioread64(devdata->mm + OFS_DBEAXILITE_SETWRITE);
		
			if (rdy == FLS64) break;
		};

		if (rdy == FLS64) {
			for (i = 0; i < len64; i++) iowrite64(le64_to_cpu(((__u64*) buf)[i]), devdata->mm + OFS_DBEAXILITE_WRITE);

			iowrite64(TRU64, devdata->mm + OFS_DBEAXILITE_SETWRITE);

			retval = reqlen;
		};// else errno = ETIMEDOUT;
	};// else errno = ENOMEM;
#else
	__u32 rdy;

	size_t len32 = reqlen / 4;
	if ((reqlen%4) > 0) len32++;

	buf = NULL;
	if (!kernelNotUser && ((4*len32) > SIZERWBUF)) {
		buf_lcl = kmalloc(4 * len32, 0);
		buf = buf_lcl;
	} else {
		buf_lcl = NULL;
		if (kernelNotUser) buf = (__u8*) buf_ext;
		else buf = devdata->rwbuf;
	};

	if (!kernelNotUser && buf) if (copy_from_user(buf, (const void __user*) buf_ext, reqlen) != 0) {
		//errno = EFAULT
		buf = NULL;
	};

	if (buf) {
		for (i = 0; i <= 20; i++) {
			// wait for max. 210us
			if (i != 0) udelay(i);

			iowrite32(FLS32, devdata->mm + OFS_DBEAXILITE_SETWRITE);
			rdy = ioread32(devdata->mm + OFS_DBEAXILITE_SETWRITE);
		
			if (rdy == FLS32) break;
		};

		if (rdy == FLS32) {
			for (i = 0; i < len32; i++) iowrite32(le32_to_cpu(((__u32*) buf)[i]), devdata->mm + OFS_DBEAXILITE_WRITE);

			iowrite32(TRU32, devdata->mm + OFS_DBEAXILITE_SETWRITE);

			retval = reqlen;
		};// else errno = ETIMEDOUT;
	};// else errno = ENOMEM;
#endif

#ifdef RXTXDUMP
	if (buf) {
		for (i = 0; (i < reqlen) && (i < 15); i++) {
			c = (buf[i] >> 4) + 0x30;
			if (c > 0x39) c += 0x07;
			devdata->rxtxdump[2*i] = (char) c;

			c = (buf[i] & 0x0F) + 0x30;
			if (c > 0x39) c += 0x07;
			devdata->rxtxdump[2*i+1] = (char) c;
		};
		i *= 2;
		if (i == 30) devdata->rxtxdump[i++] = '+';
		devdata->rxtxdump[i] = '\0';
		printk(KERN_INFO "tx %s%s\n", devdata->rxtxdump, (retval == reqlen) ? "" : " fail");
	};
#endif

	if (buf_lcl) kfree(buf_lcl);

	return retval;
};

static long runCmd(
			struct file* file
			, struct dbeaxilite_cmd_t __user* cmd
		) {
	long res;
	unsigned long flags;

	struct dbeaxilite_data_t* devdata = file->private_data;

#ifdef CONFIG_64BIT
	// actual requirement: 6/1
	__u8 rxbuf[8];
	__u8 txbuf[8];
#else
	__u8 rxbuf[8];
	__u8 txbuf[4];
#endif

	spin_lock_irqsave(&(devdata->sAccess), flags);

	res = 0;

	if (copy_from_user((void*) &(devdata->cmd), (const void __user*) cmd, sizeof(struct dbeaxilite_cmd_t)) != 0) res = -VECDBEVRESULT_nok;

	// - invoke

	// tx 1 (fixed)
	if (res == 0) if (!tx(devdata, TRUE, devdata->cmd.tx1, 9)) res = -VECDBEVRESULT_cmd_ETX1;

	// rx 1 (ack)
	if (res == 0) if (!rx(devdata, TRUE, rxbuf, 1)) res = -VECDBEVRESULT_cmd_ERX1;

	if (res == 0) if (rxbuf[0] != 0x55) res = -VECDBEVRESULT_cmd_ERX1NAK;

	if ((res == 0) && (devdata->cmd.lenParbufInv != 0)) {
		// tx 2 (varies)
		crc_reset(devdata);
		crc_includeBytes(devdata, FALSE, devdata->cmd.parbufInv, devdata->cmd.lenParbufInv);
		crc_finalize(devdata);

		setCrc(devdata, FALSE, devdata->cmd.parbufInv, devdata->cmd.lenParbufInv);

		if (!tx(devdata, FALSE, devdata->cmd.parbufInv, devdata->cmd.lenParbufInv+2)) res = -VECDBEVRESULT_cmd_ETX2;

		// rx 2 (ack)
		if (res == 0) {
			rxbuf[0] = 0x23;
			if (!rx(devdata, TRUE, rxbuf, 1)) res = -VECDBEVRESULT_cmd_ERX2;
		};

		if (res == 0) if (rxbuf[0] != 0x55) res = -VECDBEVRESULT_cmd_ERX2NAK;
	};

	if ((res == 0) & (devdata->cmd.lenParbufRet != 0)) {
		// - return

		// tx 3 (fixed)
		if (!tx(devdata, TRUE, devdata->cmd.tx3, 9)) res = -VECDBEVRESULT_cmd_ETX3;

		// rx 3 (ack)
		if (res == 0) if (!rx(devdata, TRUE, rxbuf, 1)) res = -VECDBEVRESULT_cmd_ERX3;

		if (res == 0) if (rxbuf[0] != 0x55) res = -VECDBEVRESULT_cmd_ERX3NAK;

		// rx 4 (fixed)
		if (res == 0) if (!rx(devdata, TRUE, rxbuf, 6)) res = -VECDBEVRESULT_cmd_ERX4;

		if (res == 0) {
			if (strncmp(rxbuf, devdata->cmd.rx4, 6) != 0) res = -VECDBEVRESULT_cmd_ERX4NCMD;

			if (res == 0) txbuf[0] = 0x55;
			else txbuf[0] = 0xAA;

			// tx 4 (ack)
			if (!tx(devdata, TRUE, txbuf, 1)) if (res == 0) res = -VECDBEVRESULT_cmd_ETX4;
		};

		// rx 5 (varies)
		if (res == 0) if (!rx(devdata, FALSE, devdata->cmd.parbufRet, devdata->cmd.lenParbufRet+2)) res = -VECDBEVRESULT_cmd_ERX5;

		if (res == 0) {
			crc_reset(devdata);
			crc_includeBytes(devdata, FALSE, devdata->cmd.parbufRet, devdata->cmd.lenParbufRet+2);
			crc_finalize(devdata);

			if (devdata->crc != 0x0000) res = -VECDBEVRESULT_cmd_ERX5NCRC;

			// tx 5 (ack)
			if (res == 0) txbuf[0] = 0x55;
			else txbuf[0] = 0xAA;

			if (!tx(devdata, TRUE, txbuf, 1)) if (res == 0) res = -VECDBEVRESULT_cmd_ETX5;
		};
	};

	spin_unlock_irqrestore(&(devdata->sAccess), flags);

	return(res);
};

static long runCmdMmap(
			struct file* file
			, struct dbeaxilite_cmd_t __user* cmd
		) {
	long res;
	unsigned long flags;

	struct dbeaxilite_data_t* devdata = file->private_data;

	long a0;

	__u8 tixVController, tixVCommand;

	size_t i;

#ifdef CONFIG_64BIT
	size_t len64;

	__u64 rdy;
#else
	size_t len32;

	__u32 rdy;
#endif

	spin_lock_irqsave(&(devdata->sAccess), flags);

	res = 0;

	if (copy_from_user((void*) &(devdata->cmd), (const void __user*) cmd, sizeof(struct dbeaxilite_cmd_t)) != 0) res = -VECDBEVRESULT_nok;

	if (res == 0) if (copy_from_user((void*) &tixVController, (const void __user*) (devdata->cmd.tx1 + 1), 1) != 0) res = -VECDBEVRESULT_nok;
	if (res == 0) if (copy_from_user((void*) &tixVCommand, (const void __user*) (devdata->cmd.tx1 + 2), 1) != 0) res = -VECDBEVRESULT_nok;

	// - tx
	// currently not supported: rettype immsng w/ lenParbufInv 0
	if ((res == 0) && ((cmd->lenParbufInv != 0) || (cmd->lenParbufRet == 0))) {
		a0 = (tixVController & 0x3F);
		a0 = (a0 << 6) + (tixVCommand & 0x3F);
		a0 <<= 1;
		a0 <<= 10;

#ifdef CONFIG_64BIT
		if (devdata->cmd.lenParbufInv != 0) {
			len64 = devdata->cmd.lenParbufInv / 8;
			if ((devdata->cmd.lenParbufInv%8) > 0) len64++;

			//printk(KERN_INFO "writing %d words starting at a0 = %d\n", len64, a0);

			if (copy_from_user(devdata->rwbuf, (const void __user*) (devdata->cmd.parbufInv), devdata->cmd.lenParbufInv) != 0) res = -VECDBEVRESULT_cmdmmap_ETX;

			if (res == 0) for (i = 0; i < len64; i++) iowrite64(le64_to_cpu(((__u64*) (devdata->rwbuf))[i]), devdata->mm + a0 + 8*i);
		};

		if (res == 0) iowrite64(TRU64, devdata->mm + a0 + 1024-8);
#else
		if (devdata->cmd.lenParbufInv != 0) {
			len32 = devdata->cmd.lenParbufInv / 4;
			if ((devdata->cmd.lenParbufInv%4) > 0) len32++;

			if (copy_from_user(devdata->rwbuf, (const void __user*) (devdata->cmd.parbufInv), devdata->cmd.lenParbufInv) != 0) res = -VECDBEVRESULT_cmdmmap_ETX;

			if (res == 0) for (i = 0; i < len32; i++) iowrite32(le32_to_cpu(((__u32*) (devdata->rwbuf))[i]), devdata->mm + a0 + 4*i);
		};

		if (res == 0) iowrite32(TRU32, devdata->mm + a0 + 1024-4);
#endif
	};

	// - rx
	if ((res == 0) && (devdata->cmd.lenParbufRet != 0)) {
		a0 = (tixVController & 0x3F);
		a0 = (a0 << 6) + (tixVCommand & 0x3F);
		a0 <<= 1;
		a0 += 1;
		a0 <<= 10;

#ifdef CONFIG_64BIT
		len64 = devdata->cmd.lenParbufRet / 8;
		if ((devdata->cmd.lenParbufRet%8) > 0) len64++;

		if (devdata->cmd.lenParbufInv != 0) {
			//printk(KERN_INFO "read test ready at a0 = %d\n", a0);

			for (i = 0; i <= 20; i++) {
				if (i != 0) udelay(i);

				rdy = ioread64(devdata->mm + a0 + 1024-8);

				if (rdy == TRU64) break;
			};
			if (rdy != TRU64) res = -VECDBEVRESULT_cmdmmap_ERX;
		};

		if (res == 0) {
			//printk(KERN_INFO "reading %d words starting at a0 = %d\n", len64, a0);

			for (i = 0; i < len64; i++) ((__u64*) (devdata->rwbuf))[i] = cpu_to_le64(ioread64(devdata->mm + a0 + 8*i));

			if (copy_to_user((void __user*) (devdata->cmd.parbufRet), devdata->rwbuf, devdata->cmd.lenParbufRet) != 0) res = -VECDBEVRESULT_cmdmmap_ERX;
		};
#else
		len32 = devdata->cmd.lenParbufRet / 4;
		if ((devdata->cmd.lenParbufRet%8) > 0) len32++;

		if (devdata->cmd.lenParbufInv != 0) {
			for (i = 0; i <= 20; i++) {
				if (i != 0) udelay(i);

				rdy = ioread32(devdata->mm + a0 + 1024-4);

				if (rdy == TRU32) break;
			};
			if (rdy != TRU32) res = -VECDBEVRESULT_cmdmmap_ERX;
		};

		if (res == 0) {
			for (i = 0; i < len32; i++) ((__u32*) (devdata->rwbuf))[i] = cpu_to_le32(ioread32(devdata->mm + a0 + 4*i));

			if (copy_to_user((void __user*) (devdata->cmd.parbufRet), devdata->rwbuf, devdata->cmd.lenParbufRet) != 0) res = -VECDBEVRESULT_cmdmmap_ERX;
		};
#endif
	};

	spin_unlock_irqrestore(&(devdata->sAccess), flags);

	return(res);
};

static long runCmds(
			struct file* file
			, struct dbeaxilite_cmds_t __user* cmds
		) {
	long res;

	struct dbeaxilite_data_t* devdata = file->private_data;
	
	struct dbeaxilite_cmds_t cmds_lcl;

	size_t i;
	
	res = 0;

	if (copy_from_user((void*) &cmds_lcl, (const void __user*) cmds, sizeof(struct dbeaxilite_cmds_t)) != 0) res = VECDBEVRESULT_nok;

	for (i = 0; i < cmds_lcl.NCmd; i++) {
		if (!devdata->mmapNotBus) res = runCmd(file, cmds_lcl.cmds + i * sizeof(void*));
		else  res = runCmdMmap(file, cmds_lcl.cmds + i * sizeof(void*));

		if (res != 0) break;
	};

	return res;
};

static long runBufxf(
			struct file* file
			, struct dbeaxilite_bufxf_t __user* bufxf
		) {
	long res;
	unsigned long flags;

	struct dbeaxilite_data_t* devdata = file->private_data;

#ifdef CONFIG_64BIT
	// actual requirement: 1/9
	__u8 rxbuf[8];
	__u8 txbuf[16];
#else
	__u8 rxbuf[4];
	__u8 txbuf[12];
#endif

	spin_lock_irqsave(&(devdata->sAccess), flags);

	res = 0;

	if (copy_from_user((void*) &(devdata->bufxf), (const void __user*) bufxf, sizeof(struct dbeaxilite_bufxf_t)) != 0) res = -VECDBEVRESULT_nok;

	if (devdata->bufxf.writeNotRead) {
		// tx 1
		setBuffer(txbuf, devdata->bufxf.tixVBuffer);
		setController(txbuf, 0x00);
		setBufxfop(txbuf, VECDBEVBUFXFOP_xfer);
		setLength(txbuf, devdata->bufxf.reqlen);

		crc_reset(devdata);
		crc_includeBytes(devdata, TRUE, txbuf, 7);
		crc_finalize(devdata);
		setCrc(devdata, TRUE, txbuf, 0);

		if (res == 0) if (!tx(devdata, TRUE, txbuf, 9)) res = -VECDBEVRESULT_bufxf_ETX1;

		// rx 1 (ack)
		if (res == 0) if (!rx(devdata, TRUE, rxbuf, 1)) res = -VECDBEVRESULT_bufxf_ERX1;

		if (res == 0) if (rxbuf[0] != 0x55) res = -VECDBEVRESULT_bufxf_ERX1NAK; // will be 0xAA if reqlen > avllen

		if (res == 0) {
			// tx 2
			crc_reset(devdata);
			crc_includeBytes(devdata, FALSE, devdata->bufxf.data, devdata->bufxf.reqlen);
			crc_finalize(devdata);
			setCrc(devdata, FALSE, devdata->bufxf.data, devdata->bufxf.reqlen);

			if (!tx(devdata, FALSE, devdata->bufxf.data, devdata->bufxf.reqlen+2)) res = -VECDBEVRESULT_bufxf_ETX2;
		};

		// rx 2 (ack)
		if (res == 0) if (!rx(devdata, TRUE, rxbuf, 1)) res = -VECDBEVRESULT_bufxf_ERX2;

		if (res == 0) if (rxbuf[0] != 0x55) res = -VECDBEVRESULT_bufxf_ERX2NAK;

	} else {
		// tx 1
		setBuffer(txbuf, devdata->bufxf.tixVBuffer);
		setController(txbuf, 0x00);
		setBufxfop(txbuf, VECDBEVBUFXFOP_xfer);
		setLength(txbuf, devdata->bufxf.reqlen);

		crc_reset(devdata);
		crc_includeBytes(devdata, TRUE, txbuf, 7);
		crc_finalize(devdata);
		setCrc(devdata, TRUE, txbuf, 0);

		if (res == 0) if (!tx(devdata, TRUE, txbuf, 9)) res = -VECDBEVRESULT_bufxf_ETX1;

		// rx 1 (ack)
		if (res == 0) if (!rx(devdata, TRUE, rxbuf, 1)) res = -VECDBEVRESULT_bufxf_ERX1;

		if (res == 0) if (rxbuf[0] != 0x55) res = -VECDBEVRESULT_bufxf_ERX1NAK; // will be 0xAA if reqlen != avllen

		// rx 2
		if (res == 0) if (!rx(devdata, FALSE, devdata->bufxf.data, devdata->bufxf.reqlen+2)) res = -VECDBEVRESULT_bufxf_ERX2;

		if (res == 0) {
			crc_reset(devdata);
			crc_includeBytes(devdata, FALSE, devdata->bufxf.data, devdata->bufxf.reqlen+2);
			crc_finalize(devdata);

			if (devdata->crc != 0x0000) res = -VECDBEVRESULT_bufxf_ERX2NCRC;

			// tx 2 (ack)
			if (res == 0) txbuf[0] = 0x55;
			else txbuf[0] = 0xAA;

			if (!tx(devdata, TRUE, txbuf, 1)) if (res == 0) res = -VECDBEVRESULT_bufxf_ETX2;
		};
	};

	spin_unlock_irqrestore(&(devdata->sAccess), flags);

	return(res);
};

/***** file operations *****/

static ssize_t device_read(
			struct file* file
			, char __user* buffer
			, size_t length
			, loff_t* offset
		) {
	int retval; // number of bytes read
	unsigned long flags;

	struct dbeaxilite_data_t* devdata = file->private_data;

	spin_lock_irqsave(&(devdata->sAccess), flags);

	retval = rx(devdata, FALSE, buffer, length);

	spin_unlock_irqrestore(&(devdata->sAccess), flags);

	return retval;
};

static ssize_t device_write(
			struct file* file
			, const char __user* buffer
			, size_t length
			, loff_t* offset
		) {
	int retval; // number of bytes written
	unsigned long flags;

	struct dbeaxilite_data_t* devdata = file->private_data;

	spin_lock_irqsave(&(devdata->sAccess), flags);

	retval = tx(devdata, FALSE, buffer, length);

	spin_unlock_irqrestore(&(devdata->sAccess), flags);

	return retval;
};

static long device_ioctl(
			struct file* file
			, unsigned int ioctl_num
			, unsigned long ioctl_param
		) {
	struct dbeaxilite_data_t* devdata = file->private_data;

	if (ioctl_num == DBEAXILITE_IOC_RUNCMD) {
		if (!devdata->mmapNotBus) return runCmd(file, (struct dbeaxilite_cmd_t __user*) ioctl_param);
		return runCmdMmap(file, (struct dbeaxilite_cmd_t __user*) ioctl_param);
	};
	if (ioctl_num == DBEAXILITE_IOC_RUNCMDS) return runCmds(file, (struct dbeaxilite_cmds_t __user*) ioctl_param);

	if (ioctl_num == DBEAXILITE_IOC_RUNBUFXF) return runBufxf(file, (struct dbeaxilite_bufxf_t __user*) ioctl_param);

	return -EINVAL;
};

static int device_open(
			struct inode* inode
			, struct file* file
		) {
	int ix;
	struct dbeaxilite_data_t* devdata = NULL;

	for (ix = 0; ix < NMAX_MINOR; ix++)
		if (devdatas[ix].dev == inode->i_rdev) {
			devdata = &(devdatas[ix]);
			break;
		};

	if (!devdata) return -ENXIO;

	file->private_data = devdata;

	if (devdata->busyNotIdle) return -EBUSY;

	try_module_get(THIS_MODULE);

	devdata->busyNotIdle = 1;

	return 0;
};

static int device_close(
			struct inode* inode
			, struct file* file
		) {
	struct dbeaxilite_data_t* devdata = file->private_data;

	if (!devdata->busyNotIdle) return -EBUSY;

	module_put(THIS_MODULE);

	devdata->busyNotIdle = 0;

	return 0;
};

static struct file_operations fileops = {
			.owner = THIS_MODULE
			, .read = device_read
			, .write = device_write
			, .unlocked_ioctl = device_ioctl
			, .open = device_open
			, .release = device_close
			, };

/***** module probe and remove *****/

static int dbeaxilite_probe(
			struct platform_device* pdev
		) {
	int ix;
	struct dbeaxilite_data_t* devdata = NULL;

	struct resource* r_mem;
	struct device* dev = &pdev->dev;

	for (ix = 0; ix < NMAX_MINOR; ix++)
		if (devdatas[ix].mm == NULL) {
			devdata = &(devdatas[ix]);
			break;
		};

	if (!devdata) {
		dev_err(dev, "no minor number available\n");

		return -ENODEV;
	};

	dev->devt = MKDEV(major_num, ix);
	devdata->dev = dev->devt;

	device_create(deviceFileClass, NULL, dev->devt, NULL, DRIVER_NAME "%d", ix);

#ifdef CONFIG_OF
	devdata->mmapNotBus = of_property_read_bool(dev->of_node, "mmapNotBus");
#endif

	r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!r_mem) {
		dev_err(dev, "invalid address\n");

		return -ENODEV;
	};

	if (!request_mem_region(r_mem->start, r_mem->end - r_mem->start + 1, DRIVER_NAME)) {
		dev_err(dev, "error locking memory region between 0x%10x and 0x%10x\n", (unsigned int __force) r_mem->start, (unsigned int __force) r_mem->end);

		return -EBUSY;
	};

	devdata->mm = ioremap(r_mem->start, r_mem->end - r_mem->start + 1);
	if (!devdata->mm) {
		dev_err(dev, "error mapping memory region at 0x%10x\n", (unsigned int __force) r_mem->start);
		release_mem_region(r_mem->start, r_mem->end - r_mem->start + 1);

		return -EIO;
	};

	devdata->busyNotIdle = 0;

	dev_info(dev, "dbeaxilite device (%s mode) successfully mapped to memory\n", (devdata->mmapNotBus) ? "memory-mapped" : "bus");

	return 0;
};

#if LINUX_VERSION_CODE <= KERNEL_VERSION(6,10,14)
static int dbeaxilite_remove(
#else
static void dbeaxilite_remove(
#endif
			struct platform_device* pdev
		) {
	int ix;
	struct dbeaxilite_data_t* devdata = NULL;

	struct resource* r_mem;
	struct device* dev = &pdev->dev;

	for (ix = 0; ix < NMAX_MINOR; ix++) {
		if (devdatas[ix].dev == pdev->dev.devt) {
			devdata = &(devdatas[ix]);
			break;
		};
	};

	if (!devdata) {
		dev_err(dev, "device not found\n");

#if LINUX_VERSION_CODE <= KERNEL_VERSION(6,10,14)
		return 0;
#else
		return;
#endif
	};

	iounmap(devdata->mm);

	r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!r_mem) {
		dev_err(dev, "invalid address\n");

#if LINUX_VERSION_CODE <= KERNEL_VERSION(6,10,14)
		return 0;
#else
		return;
#endif
	};

	release_mem_region(r_mem->start, r_mem->end - r_mem->start + 1);

	devdata->dev = 0;
	devdata->mm = NULL;

#if LINUX_VERSION_CODE <= KERNEL_VERSION(6,10,14)
	return 0;
#endif
};

#ifdef CONFIG_OF
	static struct of_device_id dbeaxilite_of_match[] = {{
				.compatible = "mpsi,dbeaxilite"
				, }, {}, };
	MODULE_DEVICE_TABLE(of, dbeaxilite_of_match);
#endif

static struct platform_driver plfdrv = {
			.driver = {
						.name = DRIVER_NAME
						, .owner = THIS_MODULE
#ifdef CONFIG_OF
						, .of_match_table	= dbeaxilite_of_match
#endif
						//,
			}
			, .probe = dbeaxilite_probe
			, .remove = dbeaxilite_remove//,
};

/***** main *****/

static int __init dbeaxilite_init(void) {
	int ix;

	major_num = register_chrdev(0, DRIVER_NAME, &fileops);
	if (major_num < 0) printk(KERN_ALERT "error registering character device!\n");

#if LINUX_VERSION_CODE>=KERNEL_VERSION(6, 4, 0)
	deviceFileClass = class_create(DRIVER_NAME);
#else
	deviceFileClass = class_create(THIS_MODULE, DRIVER_NAME);
#endif

	for (ix = 0; ix < NMAX_MINOR; ix++) {
		devdatas[ix].dev = 0;
		devdatas[ix].mmapNotBus = false;
		devdatas[ix].mm = NULL;
		devdatas[ix].busyNotIdle = 0;

		if (SIZERWBUF > 0) devdatas[ix].rwbuf = kmalloc(SIZERWBUF, 0);
		else devdatas[ix].rwbuf = NULL;

		spin_lock_init(&(devdatas[ix].sAccess));

		devdatas[ix].crc = 0;
	};

	return platform_driver_register(&plfdrv);
};

static void __exit dbeaxilite_exit(void) {
	int ix;

	if (SIZERWBUF > 0) for (ix = 0; ix < NMAX_MINOR; ix++) kfree(devdatas[ix].rwbuf);

	platform_driver_unregister(&plfdrv);
	unregister_chrdev(major_num, DRIVER_NAME);
};

module_init(dbeaxilite_init);
module_exit(dbeaxilite_exit);
