[PATCH linux dev-5.15 v1 3/3] misc: Add NPCM845 jtag master driver

Stanley Chu stanley.chuys at gmail.com
Thu Aug 18 16:39:51 AEST 2022


The jtag master driver provides ioctls for userspace to shift
jtag instruction/data.

Signed-off-by: Stanley Chu <yschu at nuvoton.com>
---
 drivers/misc/Kconfig               |   7 +
 drivers/misc/Makefile              |   1 +
 drivers/misc/npcm8xx-jtag-master.c | 902 +++++++++++++++++++++++++++++
 3 files changed, 910 insertions(+)
 create mode 100644 drivers/misc/npcm8xx-jtag-master.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 1174ad7a15a7..18e478050b63 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -491,6 +491,13 @@ config NPCM7XX_JTAG_MASTER
 	help
 	  Control PSPI/GPIO to transmit jtag signals to support jtag master function.
 
+config NPCM8XX_JTAG_MASTER
+	tristate "NPCM8xx JTAG Master driver"
+	depends on (ARCH_NPCM || COMPILE_TEST)
+	help
+	  Provide a driver for userspace to control the JTAG master
+	  controller to shift JTAG signals.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 0400bfee295f..879fe9fbcdc0 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -62,3 +62,4 @@ obj-$(CONFIG_HI6421V600_IRQ)	+= hi6421v600-irq.o
 obj-$(CONFIG_NPCM_LPC_BPC)	+= npcm7xx-lpc-bpc.o
 obj-$(CONFIG_NPCM_PCI_MBOX)	+= npcm7xx-pci-mbox.o
 obj-$(CONFIG_NPCM7XX_JTAG_MASTER)	+= npcm7xx-jtag-master.o
+obj-$(CONFIG_NPCM8XX_JTAG_MASTER)       += npcm8xx-jtag-master.o
diff --git a/drivers/misc/npcm8xx-jtag-master.c b/drivers/misc/npcm8xx-jtag-master.c
new file mode 100644
index 000000000000..8efa3d04ecfe
--- /dev/null
+++ b/drivers/misc/npcm8xx-jtag-master.c
@@ -0,0 +1,902 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2021 Nuvoton Technology corporation.
+
+#include <linux/kernel.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/miscdevice.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+
+/* JTM registers */
+#define JTM_CTL			0x00
+#define JTM_STAT		0x04
+#define JTM_CMD			0x08
+#define JTM_TDO_OUT(n)	(0x10 + n * 4)
+#define JTM_TDO_EN(n)	(0x20 + n * 4)
+#define JTM_TMS_OUT(n)	(0x30 + n * 4)
+#define JTM_TDI_IN(n)	(0x40 + n * 4)
+
+#define JTM_CTL_JTM_EN	BIT(0)
+#define JTM_CTL_DONE_IE	BIT(4)
+#define JTM_CTL_TRST	BIT(8)
+#define JTM_CTL_CKDV	GENMASK(23, 16)
+#define JTM_STAT_BUSY	BIT(0)
+#define JTM_STAT_DONE	BIT(1)
+#define JTM_CMD_ST_OP	BIT(0)
+#define JTM_CMD_CK_CNT	GENMASK(15, 8)
+
+#define NPCM_JTM_MAX_RATE		(50000000)
+#define NPCM_JTM_DEFAULT_RATE	(1000000)
+#define NPCM_JTM_FIFO_SIZE		128
+#define NPCM_JTM_TIMEOUT_MS		10000
+#define JTAG_MAX_XFER_DATA_LEN	65535
+#define JTAG_TLR_TMS_COUNT		9
+
+struct tck_bitbang {
+	__u8     tms;
+	__u8     tdi;
+	__u8     tdo;
+} __packed;
+
+struct bitbang_packet {
+	struct tck_bitbang *data;
+	__u32	length;
+} __packed;
+
+struct jtag_xfer {
+	__u8	type;
+	__u8	direction;
+	__u8	from;
+	__u8	endstate;
+	__u32	padding;
+	__u32	length;
+	__u64	tdio;
+};
+
+struct jtag_tap_state {
+	__u8	reset;
+	__u8	from;
+	__u8	endstate;
+	__u8	tck;
+};
+enum jtagstates {
+	jtagtlr,
+	jtagrti,
+	jtagseldr,
+	jtagcapdr,
+	jtagshfdr,
+	jtagex1dr,
+	jtagpaudr,
+	jtagex2dr,
+	jtagupddr,
+	jtagselir,
+	jtagcapir,
+	jtagshfir,
+	jtagex1ir,
+	jtagpauir,
+	jtagex2ir,
+	jtagupdir,
+	JTAG_STATE_CURRENT
+};
+
+enum jtag_reset {
+	JTAG_NO_RESET = 0,
+	JTAG_FORCE_RESET = 1,
+};
+
+enum jtag_xfer_type {
+	JTAG_SIR_XFER = 0,
+	JTAG_SDR_XFER = 1,
+	JTAG_RUNTEST_XFER = 2,
+};
+
+enum jtag_xfer_direction {
+	JTAG_READ_XFER = 1,
+	JTAG_WRITE_XFER = 2,
+	JTAG_READ_WRITE_XFER = 3,
+};
+
+#define __JTAG_IOCTL_MAGIC	0xb2
+#define JTAG_SIOCSTATE	_IOW(__JTAG_IOCTL_MAGIC, 0, struct jtag_tap_state)
+#define JTAG_SIOCFREQ	_IOW(__JTAG_IOCTL_MAGIC, 1, unsigned int)
+#define JTAG_GIOCFREQ	_IOR(__JTAG_IOCTL_MAGIC, 2, unsigned int)
+#define JTAG_IOCXFER	_IOWR(__JTAG_IOCTL_MAGIC, 3, struct jtag_xfer)
+#define JTAG_GIOCSTATUS _IOWR(__JTAG_IOCTL_MAGIC, 4, enum jtagstates)
+#define JTAG_SIOCMODE	_IOW(__JTAG_IOCTL_MAGIC, 5, unsigned int)
+#define JTAG_IOCBITBANG	_IOW(__JTAG_IOCTL_MAGIC, 6, unsigned int)
+#define JTAG_RUNTEST    _IOW(__JTAG_IOCTL_MAGIC, 7, unsigned int)
+
+static DEFINE_IDA(jtag_ida);
+static DEFINE_SPINLOCK(jtag_file_lock);
+
+struct npcm_jtm {
+	struct device *dev;
+	struct miscdevice miscdev;
+	struct reset_control *reset;
+	struct completion xfer_done;
+	struct clk *clk;
+	void __iomem *base;
+	spinlock_t lock;
+	char *tx_buf;
+	char *rx_buf;
+	char *tms_buf;
+	u32 tx_len;
+	u32 rx_len;
+	u32 ck_cnt;
+	u8 tapstate;
+	u32 freq;
+	int id;
+	bool is_open;
+	bool end_tms_high;
+};
+
+struct tmscycle {
+	unsigned char tmsbits;
+	unsigned char count;
+};
+
+/* this is the complete set TMS cycles for going from any TAP state to
+ * any other TAP state, following a “shortest path” rule
+ */
+const struct tmscycle tmscyclelookup[][16] = {
+/*      TLR        RTI       SelDR      CapDR      SDR      */
+/*      Ex1DR      PDR       Ex2DR      UpdDR      SelIR    */
+/*      CapIR      SIR       Ex1IR      PIR        Ex2IR    */
+/*      UpdIR                                               */
+/* TLR */
+	{
+		{0x01, 1}, {0x00, 1}, {0x02, 2}, {0x02, 3}, {0x02, 4},
+		{0x0a, 4}, {0x0a, 5}, {0x2a, 6}, {0x1a, 5}, {0x06, 3},
+		{0x06, 4}, {0x06, 5}, {0x16, 5}, {0x16, 6}, {0x56, 7},
+		{0x36, 6}
+	},
+/* RTI */
+	{
+		{0x07, 3}, {0x00, 1}, {0x01, 1}, {0x01, 2}, {0x01, 3},
+		{0x05, 3}, {0x05, 4}, {0x15, 5}, {0x0d, 4}, {0x03, 2},
+		{0x03, 3}, {0x03, 4}, {0x0b, 4}, {0x0b, 5}, {0x2b, 6},
+		{0x1b, 5}
+	},
+/* SelDR */
+	{
+		{0x03, 2}, {0x03, 3}, {0x00, 0}, {0x00, 1}, {0x00, 2},
+		{0x02, 2}, {0x02, 3}, {0x0a, 4}, {0x06, 3}, {0x01, 1},
+		{0x01, 2}, {0x01, 3}, {0x05, 3}, {0x05, 4}, {0x15, 5},
+		{0x0d, 4}
+	},
+/* CapDR */
+	{
+		{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x00, 0}, {0x00, 1},
+		{0x01, 1}, {0x01, 2}, {0x05, 3}, {0x03, 2}, {0x0f, 4},
+		{0x0f, 5}, {0x0f, 6}, {0x2f, 6}, {0x2f, 7}, {0xaf, 8},
+		{0x6f, 7}
+	},
+/* SDR */
+	{
+		{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x00, 0},
+		{0x01, 1}, {0x01, 2}, {0x05, 3}, {0x03, 2}, {0x0f, 4},
+		{0x0f, 5}, {0x0f, 6}, {0x2f, 6}, {0x2f, 7}, {0xaf, 8},
+		{0x6f, 7}
+	},
+/* Ex1DR */
+	{
+		{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x02, 3},
+		{0x00, 0}, {0x00, 1}, {0x02, 2}, {0x01, 1}, {0x07, 3},
+		{0x07, 4}, {0x07, 5}, {0x17, 5}, {0x17, 6}, {0x57, 7},
+		{0x37, 6}
+	},
+/* PDR */
+	{
+		{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x01, 2},
+		{0x05, 3}, {0x00, 1}, {0x01, 1}, {0x03, 2}, {0x0f, 4},
+		{0x0f, 5}, {0x0f, 6}, {0x2f, 6}, {0x2f, 7}, {0xaf, 8},
+		{0x6f, 7}
+	},
+/* Ex2DR */
+	{
+		{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x00, 1},
+		{0x02, 2}, {0x02, 3}, {0x00, 0}, {0x01, 1}, {0x07, 3},
+		{0x07, 4}, {0x07, 5}, {0x17, 5}, {0x17, 6}, {0x57, 7},
+		{0x37, 6}
+	},
+/* UpdDR */
+	{
+		{0x07, 3}, {0x00, 1}, {0x01, 1}, {0x01, 2}, {0x01, 3},
+		{0x05, 3}, {0x05, 4}, {0x15, 5}, {0x00, 0}, {0x03, 2},
+		{0x03, 3}, {0x03, 4}, {0x0b, 4}, {0x0b, 5}, {0x2b, 6},
+		{0x1b, 5}
+	},
+/* SelIR */
+	{
+		{0x01, 1}, {0x01, 2}, {0x05, 3}, {0x05, 4}, {0x05, 5},
+		{0x15, 5}, {0x15, 6}, {0x55, 7}, {0x35, 6}, {0x00, 0},
+		{0x00, 1}, {0x00, 2}, {0x02, 2}, {0x02, 3}, {0x0a, 4},
+		{0x06, 3}
+	},
+/* CapIR */
+	{
+		{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5},
+		{0x17, 5}, {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4},
+		{0x00, 0}, {0x00, 1}, {0x01, 1}, {0x01, 2}, {0x05, 3},
+		{0x03, 2}
+	},
+/* SIR */
+	{
+		{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5},
+		{0x17, 5}, {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4},
+		{0x0f, 5}, {0x00, 0}, {0x01, 1}, {0x01, 2}, {0x05, 3},
+		{0x03, 2}
+	},
+/* Ex1IR */
+	{
+		{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x03, 4},
+		{0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x1b, 5}, {0x07, 3},
+		{0x07, 4}, {0x02, 3}, {0x00, 0}, {0x00, 1}, {0x02, 2},
+		{0x01, 1}
+	},
+/* PIR */
+	{
+		{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5},
+		{0x17, 5}, {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4},
+		{0x0f, 5}, {0x01, 2}, {0x05, 3}, {0x00, 1}, {0x01, 1},
+		{0x03, 2}
+	},
+/* Ex2IR */
+	{
+		{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x03, 4},
+		{0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x1b, 5}, {0x07, 3},
+		{0x07, 4}, {0x00, 1}, {0x02, 2}, {0x02, 3}, {0x00, 0},
+		{0x01, 1}
+	},
+/* UpdIR */
+	{
+		{0x07, 3}, {0x00, 1}, {0x01, 1}, {0x01, 2}, {0x01, 3},
+		{0x05, 3}, {0x05, 4}, {0x15, 5}, {0x0d, 4}, {0x03, 2},
+		{0x03, 3}, {0x03, 4}, {0x0b, 4}, {0x0b, 5}, {0x2b, 6},
+		{0x00, 0}
+	},
+};
+
+/* should be called from atomic context */
+static int npcm_jtm_send(struct npcm_jtm *priv)
+{
+	u32 *tdo32 = (u32 *)priv->tx_buf;
+	u32 *tms32 = (u32 *)priv->tms_buf;
+	u8 *tdo8, *tms8;
+	u32 cnt;
+	u32 val, tmsval;
+	int n, i;
+	int words, bytes;
+
+	if (priv->tx_len > NPCM_JTM_FIFO_SIZE)
+		cnt = NPCM_JTM_FIFO_SIZE;
+	else
+		cnt = priv->tx_len;
+
+	words = cnt / 32;
+	bytes = DIV_ROUND_UP((cnt % 32), 8);
+
+	for (n = 0; n < words; n++) {
+		if (tdo32) {
+			writel(*tdo32, priv->base + JTM_TDO_OUT(n));
+			tdo32++;
+		} else
+			writel(0, priv->base + JTM_TDO_OUT(n));
+		if (priv->tms_buf) {
+			tmsval = *tms32;
+			tms32++;
+		} else
+			tmsval = 0;
+		if (priv->end_tms_high && (cnt == priv->tx_len)
+				&& !bytes && (n == words - 1))
+			tmsval |= (1 << 31);
+		writel(tmsval, priv->base + JTM_TMS_OUT(n));
+	}
+
+	if (bytes) {
+		tdo8 = (u8 *)tdo32;
+		tms8 = (u8 *)tms32;
+		val = 0;
+		tmsval = 0;
+		for (i = 0; i < bytes; i++) {
+			if (tdo8)
+				val |= tdo8[i] << (i * 8);
+			if (priv->tms_buf)
+				tmsval |= tms8[i] << (i * 8);
+		}
+		if (priv->end_tms_high && (cnt == priv->tx_len))
+			tmsval |= 1 << ((cnt % 32) - 1);
+		writel(val, priv->base + JTM_TDO_OUT(n));
+		writel(tmsval, priv->base + JTM_TMS_OUT(n));
+	}
+
+	priv->ck_cnt = cnt;
+	priv->tx_len -= cnt;
+	if (priv->tx_buf)
+		priv->tx_buf += cnt / 8;
+	if (priv->tms_buf)
+		priv->tms_buf += cnt / 8;
+
+	/* Start */
+	val = readl(priv->base + JTM_CMD);
+	val &= ~JTM_CMD_CK_CNT;
+	val |= (cnt << 8) | JTM_CMD_ST_OP;
+	writel(val, priv->base + JTM_CMD);
+
+	return cnt;
+}
+
+/* should be called from atomic context */
+static int npcm_jtm_recv(struct npcm_jtm *priv, u32 cnt)
+{
+	u32 *buf32 = (u32 *)priv->rx_buf;
+	u8 *buf;
+	u32 val;
+	int n, i;
+	int words, bytes;
+
+	if (priv->rx_len < cnt)
+		return -EINVAL;
+
+	words = cnt / 32;
+	bytes = DIV_ROUND_UP((cnt % 32), 8);
+	for (n = 0; n < words; n++) {
+		val = readl(priv->base + JTM_TDI_IN(n));
+		if (buf32) {
+			*buf32 = val;
+			buf32++;
+		}
+	}
+
+	if (bytes) {
+		buf = (u8 *)buf32;
+		val = readl(priv->base + JTM_TDI_IN(n));
+		if (buf)
+			for (i = 0; i < bytes; i++)
+				buf[i] = (val >> (i * 8)) & 0xFF;
+	}
+	priv->rx_len -= cnt;
+	if (priv->rx_buf)
+		priv->rx_buf += cnt / 8;
+
+	return 0;
+}
+
+static irqreturn_t npcm_jtm_handler(int irq, void *dev_id)
+{
+	struct npcm_jtm *priv = dev_id;
+	u32 stat;
+
+	stat = readl(priv->base + JTM_STAT);
+
+	if (stat & JTM_STAT_DONE) {
+		writel(JTM_STAT_DONE, priv->base + JTM_STAT);
+		if (priv->rx_len && priv->ck_cnt)
+			npcm_jtm_recv(priv, priv->ck_cnt);
+		if (priv->rx_len == 0)
+			complete(&priv->xfer_done);
+	}
+	if (((stat & JTM_STAT_BUSY) == 0)) {
+		if (priv->tx_len)
+			npcm_jtm_send(priv);
+	}
+
+	return IRQ_HANDLED;
+}
+
+/* jtm_tdo: master to target, jtm_tdi: target to master */
+static int npcm_jtm_shift(struct npcm_jtm *priv, char *jtm_tdo,
+			char *jtm_tdi, char *tms, unsigned int tcks)
+{
+	u32 val;
+	u8 stat;
+	unsigned long flags;
+	int ret = 0;
+
+	if (!tcks)
+		return -EINVAL;
+
+	priv->tx_len = tcks;
+	priv->tx_buf = jtm_tdo;
+	priv->rx_len = tcks;
+	priv->rx_buf = jtm_tdi;
+	priv->tms_buf = tms;
+
+	stat = readl(priv->base + JTM_STAT);
+	if ((stat & JTM_STAT_BUSY) != 0) {
+		dev_err(priv->dev, "jtm state busy\n");
+		return -EBUSY;
+	}
+
+	reinit_completion(&priv->xfer_done);
+	/* enable module and interrupt */
+	val = readl(priv->base + JTM_CTL);
+	val |= JTM_CTL_JTM_EN | JTM_CTL_DONE_IE;
+	writel(val, priv->base + JTM_CTL);
+
+	spin_lock_irqsave(&priv->lock, flags);
+	npcm_jtm_send(priv);
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	ret = wait_for_completion_timeout(&priv->xfer_done,
+					     msecs_to_jiffies
+					     (NPCM_JTM_TIMEOUT_MS));
+	if (ret == 0)
+		ret = -ETIMEDOUT;
+	else
+		ret = 0;
+
+	/* disable module and interrupt */
+	val &= ~(JTM_CTL_JTM_EN | JTM_CTL_DONE_IE);
+	writel(val, priv->base + JTM_CTL);
+
+	return ret;
+}
+
+static void npcm_jtm_reset_hw(struct npcm_jtm *priv)
+{
+	reset_control_assert(priv->reset);
+	udelay(5);
+	reset_control_deassert(priv->reset);
+}
+
+static void npcm_jtm_set_baudrate(struct npcm_jtm *priv, unsigned int speed)
+{
+	u32 ckdiv;
+	u32 regtemp;
+
+	ckdiv = DIV_ROUND_CLOSEST(clk_get_rate(priv->clk), (2 * speed)) - 1;
+
+	regtemp = readl(priv->base + JTM_CTL);
+	regtemp &= ~JTM_CTL_CKDV;
+	writel(regtemp | (ckdiv << 16), priv->base + JTM_CTL);
+}
+
+static int jtag_set_tapstate(struct npcm_jtm *jtag,
+				     enum jtagstates from, enum jtagstates to)
+{
+	u8 tdo[2], tdi[2], tms[2];
+	u8 count;
+	int ret;
+
+	if (from == to || to == JTAG_STATE_CURRENT)
+		return 0;
+
+	if (from > JTAG_STATE_CURRENT || to > JTAG_STATE_CURRENT)
+		return -1;
+
+	jtag->end_tms_high = false;
+	if (to == jtagtlr) {
+		tms[0] = 0xff;
+		tms[1] = 0x01;
+		tdo[0] = tdo[1] = 0;
+		ret = npcm_jtm_shift(jtag, tdo, tdi, tms, JTAG_TLR_TMS_COUNT);
+		jtag->tapstate = jtagtlr;
+		return ret;
+	}
+
+	if (from == JTAG_STATE_CURRENT)
+		from = jtag->tapstate;
+
+	tms[0] = tmscyclelookup[from][to].tmsbits;
+	count   = tmscyclelookup[from][to].count;
+
+	if (count == 0)
+		return 0;
+
+	tdo[0] = 0;
+	ret = npcm_jtm_shift(jtag, tdo, tdi, tms, count);
+	pr_debug("jtag: change state %d -> %d\n", from, to);
+	jtag->tapstate = to;
+
+	return ret;
+}
+
+static int jtag_bitbangs(struct npcm_jtm *jtag,
+			struct bitbang_packet *bitbangs,
+			struct tck_bitbang *bitbang_data)
+{
+	int ret = 0;
+	int i;
+	u8 *jtm_tdo, *jtm_tdi, *tms;
+
+	jtag->end_tms_high = false;
+	for (i = 0; i < bitbangs->length; i++) {
+		jtm_tdo = &(bitbang_data[i].tdi);
+		jtm_tdi = &(bitbang_data[i].tdo);
+		tms = &(bitbang_data[i].tms);
+		ret = npcm_jtm_shift(jtag, jtm_tdo, jtm_tdi, tms, 1);
+		if (ret != 0)
+			break;
+	}
+
+	return ret;
+}
+
+static int jtag_transfer(struct npcm_jtm *jtag,
+			     struct jtag_xfer *xfer, u8 *jtm_tdo, u32 bytes)
+{
+	u8 *jtm_tdi = NULL;
+	int ret;
+
+	if (xfer->length == 0)
+		return 0;
+
+	if (xfer->type != JTAG_RUNTEST_XFER) {
+		jtm_tdi = kzalloc(bytes, GFP_KERNEL);
+		if (!jtm_tdi)
+			return -ENOMEM;
+	}
+
+	if (xfer->type == JTAG_SIR_XFER)
+		jtag_set_tapstate(jtag, xfer->from, jtagshfir);
+	else if (xfer->type == JTAG_SDR_XFER)
+		jtag_set_tapstate(jtag, xfer->from, jtagshfdr);
+	else if (xfer->type == JTAG_RUNTEST_XFER)
+		jtag_set_tapstate(jtag, xfer->from, jtagrti);
+
+	/* SIR/SDR: the last bit should be shifted with TMS high */
+	if ((xfer->type == JTAG_SIR_XFER && xfer->endstate != jtagshfir) ||
+			(xfer->type == JTAG_SDR_XFER && xfer->endstate != jtagshfdr)) {
+		jtag->end_tms_high = true;
+		jtag->tapstate = (jtag->tapstate == jtagshfdr) ?
+				jtagex1dr : jtagex1ir;
+	} else
+		jtag->end_tms_high = false;
+
+	ret = npcm_jtm_shift(jtag, jtm_tdo, jtm_tdi, NULL, xfer->length);
+	jtag_set_tapstate(jtag, JTAG_STATE_CURRENT, xfer->endstate);
+
+	if (xfer->type != JTAG_RUNTEST_XFER) {
+		if (jtm_tdo && !ret)
+			memcpy(jtm_tdo, jtm_tdi, bytes);
+		kfree(jtm_tdi);
+	}
+
+	return ret;
+}
+
+/* Run in rti state for specific number of tcks */
+static int jtag_runtest(struct npcm_jtm *jtag, unsigned int tcks)
+{
+	struct jtag_xfer xfer;
+	u32 bytes = DIV_ROUND_UP(tcks, BITS_PER_BYTE);
+	int ret;
+
+	xfer.type = JTAG_RUNTEST_XFER;
+	xfer.direction = JTAG_WRITE_XFER;
+	xfer.from = JTAG_STATE_CURRENT;
+	xfer.endstate = jtagrti;
+	xfer.length = tcks;
+
+	ret = jtag_transfer(jtag, &xfer, NULL, bytes);
+
+	return ret;
+}
+
+static long jtag_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct npcm_jtm *priv = file->private_data;
+	struct jtag_tap_state tapstate;
+	struct jtag_xfer xfer;
+	struct bitbang_packet bitbang;
+	struct tck_bitbang *bitbang_data;
+	u8 *xfer_data;
+	u32 data_size;
+	u32 value;
+	int ret = 0;
+
+	switch (cmd) {
+	case JTAG_SIOCFREQ:
+		if (get_user(value, (__u32 __user *)arg))
+			return -EFAULT;
+		if (value <= NPCM_JTM_MAX_RATE) {
+			priv->freq = value;
+			npcm_jtm_set_baudrate(priv, priv->freq);
+		} else {
+			dev_err(priv->dev, "invalid jtag freq %u\n", value);
+			ret = -EINVAL;
+		}
+		break;
+	case JTAG_GIOCFREQ:
+		if (put_user(priv->freq, (__u32 __user *)arg))
+			return -EFAULT;
+		break;
+	case JTAG_IOCBITBANG:
+		if (copy_from_user(&bitbang, (const void __user *)arg,
+				   sizeof(struct bitbang_packet)))
+			return -EFAULT;
+
+		if (bitbang.length >= JTAG_MAX_XFER_DATA_LEN)
+			return -EINVAL;
+
+		data_size = bitbang.length * sizeof(struct tck_bitbang);
+		bitbang_data = memdup_user((void __user *)bitbang.data,
+					   data_size);
+		if (IS_ERR(bitbang_data))
+			return -EFAULT;
+
+		ret = jtag_bitbangs(priv, &bitbang, bitbang_data);
+		if (ret) {
+			kfree(bitbang_data);
+			return -EIO;
+		}
+		ret = copy_to_user((void __user *)bitbang.data,
+				   (void *)bitbang_data, data_size);
+		kfree(bitbang_data);
+		if (ret)
+			return -EFAULT;
+		break;
+	case JTAG_SIOCSTATE:
+		if (copy_from_user(&tapstate, (const void __user *)arg,
+				   sizeof(struct jtag_tap_state)))
+			return -EFAULT;
+
+		if (tapstate.from > JTAG_STATE_CURRENT)
+			return -EINVAL;
+
+		if (tapstate.endstate > JTAG_STATE_CURRENT)
+			return -EINVAL;
+
+		if (tapstate.reset > JTAG_FORCE_RESET)
+			return -EINVAL;
+		if (tapstate.reset == JTAG_FORCE_RESET)
+			jtag_set_tapstate(priv, JTAG_STATE_CURRENT,
+						  jtagtlr);
+		jtag_set_tapstate(priv, tapstate.from,
+					  tapstate.endstate);
+		break;
+	case JTAG_GIOCSTATUS:
+		ret = put_user(priv->tapstate, (__u32 __user *)arg);
+		break;
+	case JTAG_IOCXFER:
+
+		if (copy_from_user(&xfer, (const void __user *)arg,
+			sizeof(struct jtag_xfer)))
+			return -EFAULT;
+
+		if (xfer.length >= JTAG_MAX_XFER_DATA_LEN)
+			return -EINVAL;
+
+		if (xfer.type > JTAG_SDR_XFER)
+			return -EINVAL;
+
+		if (xfer.direction > JTAG_READ_WRITE_XFER)
+			return -EINVAL;
+
+		if (xfer.from > JTAG_STATE_CURRENT)
+			return -EINVAL;
+
+		if (xfer.endstate > JTAG_STATE_CURRENT)
+			return -EINVAL;
+
+		data_size = DIV_ROUND_UP(xfer.length, BITS_PER_BYTE);
+		xfer_data = memdup_user((void __user *)xfer.tdio, data_size);
+		if (IS_ERR(xfer_data))
+			return -EFAULT;
+
+		ret = jtag_transfer(priv, &xfer, xfer_data, data_size);
+		if (ret) {
+			kfree(xfer_data);
+			return -EIO;
+		}
+		ret = copy_to_user((void __user *)xfer.tdio,
+				   (void *)xfer_data, data_size);
+		kfree(xfer_data);
+		if (ret)
+			return -EFAULT;
+
+		if (copy_to_user((void __user *)arg, (void *)&xfer,
+				 sizeof(struct jtag_xfer)))
+			return -EFAULT;
+		break;
+	case JTAG_SIOCMODE:
+		break;
+	case JTAG_RUNTEST:
+		ret = jtag_runtest(priv, (unsigned int)arg);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static int jtag_open(struct inode *inode, struct file *file)
+{
+	struct npcm_jtm *jtag;
+
+	jtag = container_of(file->private_data, struct npcm_jtm, miscdev);
+
+	spin_lock(&jtag_file_lock);
+	if (jtag->is_open) {
+		spin_unlock(&jtag_file_lock);
+		return -EBUSY;
+	}
+
+	jtag->is_open = true;
+	file->private_data = jtag;
+	spin_unlock(&jtag_file_lock);
+
+	jtag_set_tapstate(jtag, JTAG_STATE_CURRENT, jtagtlr);
+
+	return 0;
+}
+
+static int jtag_release(struct inode *inode, struct file *file)
+{
+	struct npcm_jtm *jtag = file->private_data;
+
+	spin_lock(&jtag_file_lock);
+	jtag->is_open = false;
+	spin_unlock(&jtag_file_lock);
+
+	return 0;
+}
+
+const struct file_operations npcm_jtag_fops = {
+	.open              = jtag_open,
+	.unlocked_ioctl    = jtag_ioctl,
+	.release           = jtag_release,
+};
+
+static int jtag_register_device(struct npcm_jtm *jtag)
+{
+	struct device *dev = jtag->dev;
+	int err;
+	int id;
+
+	if (!dev)
+		return -ENODEV;
+
+	id = ida_simple_get(&jtag_ida, 0, 0, GFP_KERNEL);
+	if (id < 0)
+		return id;
+
+	jtag->id = id;
+	jtag->miscdev.parent = dev;
+	jtag->miscdev.fops =  &npcm_jtag_fops;
+	jtag->miscdev.minor = MISC_DYNAMIC_MINOR;
+	jtag->miscdev.name = kasprintf(GFP_KERNEL, "jtag%d", id);
+	if (!jtag->miscdev.name) {
+		err = -ENOMEM;
+		goto err;
+	}
+
+	err = misc_register(&jtag->miscdev);
+	if (err) {
+		dev_err(jtag->miscdev.parent,
+			"Unable to register device, err %d\n", err);
+		kfree(jtag->miscdev.name);
+		goto err;
+	}
+
+	return 0;
+
+err:
+	ida_simple_remove(&jtag_ida, id);
+	return err;
+}
+
+static int npcm_jtm_probe(struct platform_device *pdev)
+{
+	struct npcm_jtm *priv;
+	unsigned long clk_hz;
+	int irq;
+	int ret;
+
+	dev_info(&pdev->dev, "%s\n", __func__);
+
+	priv = kzalloc(sizeof(struct npcm_jtm), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	priv->dev = &pdev->dev;
+
+	priv->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(priv->base)) {
+		ret = PTR_ERR(priv->base);
+		goto out_free_mem;
+	}
+
+	priv->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(priv->clk)) {
+		dev_err(&pdev->dev, "failed to get clock\n");
+		ret = PTR_ERR(priv->clk);
+		goto out_free_mem;
+	}
+
+	ret = clk_prepare_enable(priv->clk);
+	if (ret)
+		goto out_free_mem;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		ret = irq;
+		goto out_disable_clk;
+	}
+
+	priv->reset = devm_reset_control_get(&pdev->dev, NULL);
+	if (IS_ERR(priv->reset)) {
+		ret = PTR_ERR(priv->reset);
+		goto out_disable_clk;
+	}
+
+	/* reset JTM-HW block */
+	npcm_jtm_reset_hw(priv);
+
+	ret = devm_request_irq(&pdev->dev, irq, npcm_jtm_handler, 0,
+			       "npcm-jtm", priv);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to request IRQ\n");
+		goto out_disable_clk;
+	}
+
+	init_completion(&priv->xfer_done);
+
+	clk_hz = clk_get_rate(priv->clk);
+
+	priv->freq = NPCM_JTM_DEFAULT_RATE;
+	npcm_jtm_set_baudrate(priv, NPCM_JTM_DEFAULT_RATE);
+
+	ret = jtag_register_device(priv);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to create device\n");
+		goto out_disable_clk;
+	}
+	platform_set_drvdata(pdev, priv);
+
+	return 0;
+
+out_disable_clk:
+	clk_disable_unprepare(priv->clk);
+
+out_free_mem:
+	kfree(priv);
+	return ret;
+}
+
+static int npcm_jtm_remove(struct platform_device *pdev)
+{
+	struct npcm_jtm *jtag = platform_get_drvdata(pdev);
+
+	if (!jtag)
+		return 0;
+
+	misc_deregister(&jtag->miscdev);
+	kfree(jtag->miscdev.name);
+	kfree(jtag);
+	ida_simple_remove(&jtag_ida, jtag->id);
+
+	return 0;
+}
+
+static const struct of_device_id npcm_jtm_id[] = {
+	{ .compatible = "nuvoton,npcm845-jtm", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, npcm_jtm_id);
+
+static struct platform_driver npcm8xx_jtm_driver = {
+	.probe          = npcm_jtm_probe,
+	.remove			= npcm_jtm_remove,
+	.driver         = {
+		.name   = "jtag-master",
+		.owner	= THIS_MODULE,
+		.of_match_table = npcm_jtm_id,
+	},
+};
+
+module_platform_driver(npcm8xx_jtm_driver);
+
+MODULE_AUTHOR("Stanley Chu <yschu at nuvoton.com>");
+MODULE_DESCRIPTION("NPCM8xx JTAG Master Driver");
+MODULE_LICENSE("GPL v2");
-- 
2.17.1



More information about the openbmc mailing list