[dev-4.7 patch 04/11] pinctrl (aspeed) introduce Aspeed SoC jtag driver
vadimp at mellanox.com
vadimp at mellanox.com
Wed Aug 10 17:03:22 AEST 2016
From: Vadim Pasternak <vadimp at mellanox.com>
The Kconfig controlling compilation of this code is:
drivers/pinctrl/aspeed/Kconfig:config PINCTRL_ASPEED_JTAG
Porting peci driver from 3.18 to 4.7.
Signed-off-by: Vadim Pasternak <vadimp at mellanox.com>
---
drivers/pinctrl/aspeed/Kconfig | 6 +
drivers/pinctrl/aspeed/Makefile | 4 +
drivers/pinctrl/aspeed/aspeed-jtag.c | 919 +++++++++++++++++++++++++++++++++++
3 files changed, 929 insertions(+)
create mode 100644 drivers/pinctrl/aspeed/aspeed-jtag.c
diff --git a/drivers/pinctrl/aspeed/Kconfig b/drivers/pinctrl/aspeed/Kconfig
index ee45a96..0d289ab 100644
--- a/drivers/pinctrl/aspeed/Kconfig
+++ b/drivers/pinctrl/aspeed/Kconfig
@@ -22,3 +22,9 @@ config PINCTRL_ASPEED_G5
help
Say Y here to enable pin controller support for Aspeed's 5th
generation AST SoCs. GPIO is provided by a separate GPIO driver.
+
+config PINCTRL_ASPEED_JTAG
+ tristate "ASPEED JTAG control"
+ default n
+ help
+ Driver for ASPEED JTAG controller driver
diff --git a/drivers/pinctrl/aspeed/Makefile b/drivers/pinctrl/aspeed/Makefile
index 0f4b876..e229fd1 100644
--- a/drivers/pinctrl/aspeed/Makefile
+++ b/drivers/pinctrl/aspeed/Makefile
@@ -1,5 +1,9 @@
# Aspeed pinctrl support
+ccflags-$(CONFIG_ARCH_MULTIPLATFORM) := \
+ -I$(srctree)/arch/arm/mach-aspeed/include
+
obj-$(CONFIG_PINCTRL_ASPEED) += pinctrl-aspeed.o
obj-$(CONFIG_PINCTRL_ASPEED_G4) += pinctrl-aspeed-g4.o
obj-$(CONFIG_PINCTRL_ASPEED_G5) += pinctrl-aspeed-g5.o
+obj-$(CONFIG_PINCTRL_ASPEED_JTAG) += aspeed-jtag.o
diff --git a/drivers/pinctrl/aspeed/aspeed-jtag.c b/drivers/pinctrl/aspeed/aspeed-jtag.c
new file mode 100644
index 0000000..11ac953
--- /dev/null
+++ b/drivers/pinctrl/aspeed/aspeed-jtag.c
@@ -0,0 +1,919 @@
+/*
+ * driver/pincntrl/aspeed_jtag.c
+ *
+ * ASPEED JTAG controller driver
+ *
+ * Copyright (C) 2012-2020 ASPEED Technology Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * History:
+ * 2012.08.06: Initial version [Ryan Chen]
+ * 2016.0806: Porting to kernel 4.7
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/sysfs.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <asm/mach-types.h>
+#include <asm/mach/arch.h>
+#include <mach/reset.h>
+
+#define AST_JTAG_DATA 0x00
+#define AST_JTAG_INST 0x04
+#define AST_JTAG_CTRL 0x08
+#define AST_JTAG_ISR 0x0C
+#define AST_JTAG_SW 0x10
+#define AST_JTAG_TCK 0x14
+#define AST_JTAG_IDLE 0x18
+
+/* AST_JTAG_CTRL - 0x08 : Engine Control */
+#define JTAG_ENG_EN (0x1 << 31)
+#define JTAG_ENG_OUT_EN (0x1 << 30)
+#define JTAG_FORCE_TMS (0x1 << 29)
+#define JTAG_IR_UPDATE (0x1 << 26)
+#define JTAG_INST_LEN_MASK (0x3f << 20)
+#define JTAG_SET_INST_LEN(x) (x << 20)
+#define JTAG_SET_INST_MSB (0x1 << 19)
+#define JTAG_TERMINATE_INST (0x1 << 18)
+#define JTAG_LAST_INST (0x1 << 17)
+#define JTAG_INST_EN (0x1 << 16)
+#define JTAG_DATA_LEN_MASK (0x3f << 4)
+#define JTAG_DR_UPDATE (0x1 << 10)
+#define JTAG_DATA_LEN(x) (x << 4)
+#define JTAG_SET_DATA_MSB (0x1 << 3)
+#define JTAG_TERMINATE_DATA (0x1 << 2)
+#define JTAG_LAST_DATA (0x1 << 1)
+#define JTAG_DATA_EN (0x1)
+
+/* AST_JTAG_ISR - 0x0C : INterrupt status and enable */
+#define JTAG_INST_PAUSE (0x1 << 19)
+#define JTAG_INST_COMPLETE (0x1 << 18)
+#define JTAG_DATA_PAUSE (0x1 << 17)
+#define JTAG_DATA_COMPLETE (0x1 << 16)
+
+#define JTAG_INST_PAUSE_EN (0x1 << 3)
+#define JTAG_INST_COMPLETE_EN (0x1 << 2)
+#define JTAG_DATA_PAUSE_EN (0x1 << 1)
+#define JTAG_DATA_COMPLETE_EN (0x1)
+
+/* AST_JTAG_SW - 0x10 : Software Mode and Status */
+#define JTAG_SW_MODE_EN (0x1 << 19)
+#define JTAG_SW_MODE_TCK (0x1 << 18)
+#define JTAG_SW_MODE_TMS (0x1 << 17)
+#define JTAG_SW_MODE_TDIO (0x1 << 16)
+#define JTAG_STS_INST_PAUSE (0x1 << 2)
+#define JTAG_STS_DATA_PAUSE (0x1 << 1)
+#define JTAG_STS_ENG_IDLE (0x1)
+
+/* AST_JTAG_TCK - 0x14 : TCK Control */
+#define JTAG_TCK_INVERSE (0x1 << 31)
+#define JTAG_TCK_DIVISOR_MASK (0x7ff)
+#define JTAG_GET_TCK_DIVISOR(x) (x & 0x7ff)
+
+/* AST_JTAG_IDLE - 0x18 : Ctroller set for go to IDLE */
+#define JTAG_GO_IDLE (0x1)
+
+enum aspeed_jtag_xfer_mode {
+ JTAG_XFER_HW_MODE = 0,
+ JTAG_XFER_SW_MODE = 1,
+};
+
+struct runtest_idle {
+ enum aspeed_jtag_xfer_mode mode; /* 0 :HW mode, 1: SW mode */
+ unsigned char reset; /* Test Logic Reset */
+ unsigned char end; /* o: idle, 1: ir pause, 2: drpause */
+ unsigned char tck; /* keep tck */
+};
+
+struct sir_xfer {
+ enum aspeed_jtag_xfer_mode mode; /* 0 :HW mode, 1: SW mode */
+ unsigned short length; /* bits */
+ unsigned int tdi;
+ unsigned int tdo;
+ unsigned char endir; /* 0: idle, 1:pause */
+};
+
+struct sdr_xfer {
+ enum aspeed_jtag_xfer_mode mode; /* 0 :HW mode, 1: SW mode */
+ unsigned char direct; /* 0 ; read , 1 : write */
+ unsigned short length; /* bits */
+ unsigned int *tdio;
+ unsigned char enddr; /* 0: idle, 1:pause */
+};
+
+#define JTAGIOC_BASE 'T'
+
+#define AST_JTAG_IOCRUNTEST _IOW(JTAGIOC_BASE, 0, struct runtest_idle)
+#define AST_JTAG_IOCSIR _IOWR(JTAGIOC_BASE, 1, struct sir_xfer)
+#define AST_JTAG_IOCSDR _IOWR(JTAGIOC_BASE, 2, struct sdr_xfer)
+#define AST_JTAG_SIOCFREQ _IOW(JTAGIOC_BASE, 3, unsigned int)
+#define AST_JTAG_GIOCFREQ _IOR(JTAGIOC_BASE, 4, unsigned int)
+
+struct aspeed_jtag_info {
+ void __iomem *reg_base;
+ struct device *dev;
+ u8 sts; /* 0: idle, 1:irpause 2:drpause */
+ int irq; /* JTAG IRQ number */
+ u32 flag;
+ wait_queue_head_t jtag_wq;
+ bool is_open;
+};
+
+struct aspeed_jtag_info *aspeed_jtag;
+static DEFINE_SPINLOCK(jtag_state_lock);
+
+static inline u32
+aspeed_jtag_read(struct aspeed_jtag_info *aspeed_jtag, u32 reg)
+{
+ return readl(aspeed_jtag->reg_base + reg);
+}
+
+static inline void
+aspeed_jtag_write(struct aspeed_jtag_info *aspeed_jtag, u32 val, u32 reg)
+{
+ dev_dbg(aspeed_jtag->dev, "reg = 0x%08x, val = 0x%08x\n", reg, val);
+ writel(val, aspeed_jtag->reg_base + reg);
+}
+
+u32 ast_get_pclk(void)
+{
+ return 0;
+}
+
+void
+aspeed_jtag_set_freq(struct aspeed_jtag_info *aspeed_jtag, unsigned int freq)
+{
+ u16 i;
+
+ for (i = 0; i < 0x7ff; i++) {
+ if ((ast_get_pclk() / (i + 1)) <= freq)
+ break;
+ }
+ aspeed_jtag_write(aspeed_jtag,
+ ((aspeed_jtag_read(aspeed_jtag, AST_JTAG_TCK) &
+ ~JTAG_TCK_DIVISOR_MASK) | i),
+ AST_JTAG_TCK);
+}
+
+unsigned int aspeed_jtag_get_freq(struct aspeed_jtag_info *aspeed_jtag)
+{
+ return ast_get_pclk() /
+ (JTAG_GET_TCK_DIVISOR(aspeed_jtag_read(aspeed_jtag,
+ AST_JTAG_TCK)) + 1);
+}
+
+void dummy(struct aspeed_jtag_info *aspeed_jtag, unsigned int cnt)
+{
+ int i = 0;
+
+ for (i = 0; i < cnt; i++)
+ aspeed_jtag_read(aspeed_jtag, AST_JTAG_SW);
+}
+
+static u8 TCK_Cycle(struct aspeed_jtag_info *aspeed_jtag, u8 TMS, u8 TDI)
+{
+ u8 tdo;
+
+ /* TCK = 0 */
+ aspeed_jtag_write(aspeed_jtag, JTAG_SW_MODE_EN |
+ (TMS * JTAG_SW_MODE_TMS) | (TDI * JTAG_SW_MODE_TDIO),
+ AST_JTAG_SW);
+
+ dummy(aspeed_jtag, 10);
+
+ /* TCK = 1 */
+ aspeed_jtag_write(aspeed_jtag, JTAG_SW_MODE_EN | JTAG_SW_MODE_TCK |
+ (TMS * JTAG_SW_MODE_TMS) | (TDI * JTAG_SW_MODE_TDIO),
+ AST_JTAG_SW);
+
+ if (aspeed_jtag_read(aspeed_jtag, AST_JTAG_SW) & JTAG_SW_MODE_TDIO)
+ tdo = 1;
+ else
+ tdo = 0;
+
+ dummy(aspeed_jtag, 10);
+
+ /* TCK = 0 */
+ aspeed_jtag_write(aspeed_jtag, JTAG_SW_MODE_EN | (TMS *
+ JTAG_SW_MODE_TMS) | (TDI * JTAG_SW_MODE_TDIO),
+ AST_JTAG_SW);
+
+ return tdo;
+}
+
+void aspeed_jtag_wait_instruction_complete(struct aspeed_jtag_info *aspeed_jtag)
+{
+ wait_event_interruptible(aspeed_jtag->jtag_wq, (aspeed_jtag->flag ==
+ JTAG_INST_COMPLETE));
+ dev_err(aspeed_jtag->dev, "\n");
+ aspeed_jtag->flag = 0;
+}
+
+void aspeed_jtag_wait_data_pause_complete(struct aspeed_jtag_info *aspeed_jtag)
+{
+ wait_event_interruptible(aspeed_jtag->jtag_wq, (aspeed_jtag->flag ==
+ JTAG_DATA_PAUSE));
+ dev_err(aspeed_jtag->dev, "\n");
+ aspeed_jtag->flag = 0;
+}
+
+void aspeed_jtag_wait_data_complete(struct aspeed_jtag_info *aspeed_jtag)
+{
+ wait_event_interruptible(aspeed_jtag->jtag_wq, (aspeed_jtag->flag ==
+ JTAG_DATA_COMPLETE));
+ dev_err(aspeed_jtag->dev, "\n");
+ aspeed_jtag->flag = 0;
+}
+
+/* JTAG_reset() is to generate at least 9 TMS high and 1 TMS low to force
+ * devices into Run-Test/Idle State.
+ */
+void aspeed_jtag_run_test_idle(struct aspeed_jtag_info *aspeed_jtag,
+ struct runtest_idle *runtest)
+{
+ int i = 0;
+
+ dev_dbg(aspeed_jtag->dev, ":%s mode\n", runtest->mode ? "SW":"HW");
+
+ if (runtest->mode) {
+ /* SW mode from idle , from pause, -- > to pause, to idle */
+ if (runtest->reset) {
+ for (i = 0; i < 10; i++)
+ TCK_Cycle(aspeed_jtag, 1, 0);
+ }
+
+ switch (aspeed_jtag->sts) {
+ case 0:
+ if (runtest->end == 1) {
+ TCK_Cycle(aspeed_jtag, 1, 0); /* DRSCan */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* IRSCan */
+ TCK_Cycle(aspeed_jtag, 0, 0); /* IRCap */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* IRExit1 */
+ TCK_Cycle(aspeed_jtag, 0, 0); /* IRPause */
+ aspeed_jtag->sts = 1;
+ } else if (runtest->end == 2) {
+ TCK_Cycle(aspeed_jtag, 1, 0); /* DRSCan */
+ TCK_Cycle(aspeed_jtag, 0, 0); /* DRCap */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* DRExit1 */
+ TCK_Cycle(aspeed_jtag, 0, 0); /* DRPause */
+ aspeed_jtag->sts = 1;
+ } else {
+ TCK_Cycle(aspeed_jtag, 0, 0);/* IDLE */
+ aspeed_jtag->sts = 0;
+ }
+ break;
+
+ case 1:
+ /* from IR/DR Pause */
+ if (runtest->end == 1) {
+ TCK_Cycle(aspeed_jtag, 1, 0); /* Exit2 IR/DR */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* Updt IR/DR */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* DRSCan */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* IRSCan */
+ TCK_Cycle(aspeed_jtag, 0, 0); /* IRCap */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* IRExit1 */
+ TCK_Cycle(aspeed_jtag, 0, 0); /* IRPause */
+ aspeed_jtag->sts = 1;
+ } else if (runtest->end == 2) {
+ TCK_Cycle(aspeed_jtag, 1, 0); /* Exit2 IR/DR */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* Update IR/DR */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* DRSCan */
+ TCK_Cycle(aspeed_jtag, 0, 0); /* DRCap */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* DRExit1 */
+ TCK_Cycle(aspeed_jtag, 0, 0); /* DRPause */
+ aspeed_jtag->sts = 1;
+ } else {
+ TCK_Cycle(aspeed_jtag, 1, 0); /* Exit2 IR/DR */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* Updt IR/DR */
+ TCK_Cycle(aspeed_jtag, 0, 0); /* IDLE */
+ aspeed_jtag->sts = 0;
+ }
+ break;
+
+ default:
+ dev_err(aspeed_jtag->dev,
+ "aspeed_jtag_run_test_idle error\n");
+ break;
+ }
+
+ /* stay on IDLE for at least TCK cycle */
+ for (i = 0; i < runtest->tck; i++)
+ TCK_Cycle(aspeed_jtag, 0, 0);
+ } else {
+ /* dis sw mode */
+ aspeed_jtag_write(aspeed_jtag, 0, AST_JTAG_SW);
+ mdelay(1);
+ /* x TMS high + 1 TMS low */
+ if (runtest->reset)
+ aspeed_jtag_write(aspeed_jtag, JTAG_ENG_EN |
+ JTAG_ENG_OUT_EN | JTAG_FORCE_TMS,
+ AST_JTAG_CTRL);
+ else
+ aspeed_jtag_write(aspeed_jtag, JTAG_GO_IDLE,
+ AST_JTAG_IDLE);
+ mdelay(1);
+ aspeed_jtag_write(aspeed_jtag, JTAG_SW_MODE_EN |
+ JTAG_SW_MODE_TDIO, AST_JTAG_SW);
+ aspeed_jtag->sts = 0;
+ }
+}
+
+int aspeed_jtag_sir_xfer(struct aspeed_jtag_info *aspeed_jtag,
+ struct sir_xfer *sir)
+{
+ int i = 0;
+
+ dev_err(aspeed_jtag->dev, "%s mode, ENDIR : %d, len : %d\n",
+ sir->mode ? "SW":"HW", sir->endir, sir->length);
+
+ if (sir->mode) {
+ if (aspeed_jtag->sts) {
+ /* from IR/DR Pause */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* Exit2 IR / DR */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* Update IR /DR */
+ }
+
+ TCK_Cycle(aspeed_jtag, 1, 0); /* DRSCan */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* IRSCan */
+ TCK_Cycle(aspeed_jtag, 0, 0); /* CapIR */
+ TCK_Cycle(aspeed_jtag, 0, 0); /* ShiftIR */
+
+ sir->tdo = 0;
+ for (i = 0; i < sir->length; i++) {
+ if (i == (sir->length - 1)) {
+ /* IRExit1 */
+ sir->tdo |= TCK_Cycle(aspeed_jtag, 1,
+ sir->tdi & 0x1);
+ } else {
+ /* ShiftIR */
+ sir->tdo |= TCK_Cycle(aspeed_jtag, 0,
+ sir->tdi & 0x1);
+ sir->tdi >>= 1;
+ sir->tdo <<= 1;
+ }
+ }
+
+ TCK_Cycle(aspeed_jtag, 0, 0); /* IRPause */
+
+ /* stop pause */
+ if (sir->endir == 0) {
+ /* go to idle */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* IRExit2 */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* IRUpdate */
+ TCK_Cycle(aspeed_jtag, 0, 0); /* IDLE */
+ }
+ } else {
+ /* hw mode - disable sw mode */
+ aspeed_jtag_write(aspeed_jtag, 0, AST_JTAG_SW);
+ aspeed_jtag_write(aspeed_jtag, sir->tdi, AST_JTAG_INST);
+
+ if (sir->endir) {
+ aspeed_jtag_write(aspeed_jtag, JTAG_ENG_EN |
+ JTAG_ENG_OUT_EN |
+ JTAG_SET_INST_LEN(sir->length),
+ AST_JTAG_CTRL);
+ aspeed_jtag_write(aspeed_jtag, JTAG_ENG_EN |
+ JTAG_ENG_OUT_EN |
+ JTAG_SET_INST_LEN(sir->length) |
+ JTAG_INST_EN, AST_JTAG_CTRL);
+ } else {
+ aspeed_jtag_write(aspeed_jtag, JTAG_ENG_EN |
+ JTAG_ENG_OUT_EN | JTAG_LAST_INST |
+ JTAG_SET_INST_LEN(sir->length),
+ AST_JTAG_CTRL);
+ aspeed_jtag_write(aspeed_jtag, JTAG_ENG_EN |
+ JTAG_ENG_OUT_EN | JTAG_LAST_INST |
+ JTAG_SET_INST_LEN(sir->length) |
+ JTAG_INST_EN, AST_JTAG_CTRL);
+ }
+
+ aspeed_jtag_wait_instruction_complete(aspeed_jtag);
+ sir->tdo = aspeed_jtag_read(aspeed_jtag, AST_JTAG_INST);
+ aspeed_jtag_write(aspeed_jtag, JTAG_SW_MODE_EN |
+ JTAG_SW_MODE_TDIO, AST_JTAG_SW);
+ }
+
+ aspeed_jtag->sts = sir->endir;
+
+ return 0;
+}
+
+int aspeed_jtag_sdr_xfer(struct aspeed_jtag_info *aspeed_jtag,
+ struct sdr_xfer *sdr)
+{
+ unsigned int index = 0;
+ u32 shift_bits = 0;
+ u32 tdo = 0;
+ u32 remain_xfer = sdr->length;
+
+ dev_err(aspeed_jtag->dev, "%s mode, ENDDR : %d, len : %d\n",
+ sdr->mode ? "SW":"HW", sdr->enddr, sdr->length);
+
+ if (sdr->mode) {
+ /* SW mode */
+ if (aspeed_jtag->sts) {
+ /* from IR/DR Pause */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* Exit2 IR / DR */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* Update IR /DR */
+ }
+
+ TCK_Cycle(aspeed_jtag, 1, 0); /* DRScan */
+ TCK_Cycle(aspeed_jtag, 0, 0); /* DRCap */
+ TCK_Cycle(aspeed_jtag, 0, 0); /* DRShift */
+
+ while (remain_xfer) {
+ if (sdr->direct) {
+ /* write */
+ if ((shift_bits % 32) == 0)
+ dev_err(aspeed_jtag->dev, "W dr->dr_data[%d]: %x\n",
+ index, sdr->tdio[index]);
+
+ tdo = (sdr->tdio[index] >> (shift_bits % 32)) &
+ 0x1;
+ dev_err(aspeed_jtag->dev, "%d\n", tdo);
+ if (remain_xfer == 1)
+ /* DRExit1 */
+ TCK_Cycle(aspeed_jtag, 1, tdo);
+ else
+ /* DRShit */
+ TCK_Cycle(aspeed_jtag, 0, tdo);
+ } else {
+ /* read */
+ if (remain_xfer == 1)
+ /* DRExit1 */
+ tdo = TCK_Cycle(aspeed_jtag, 1, tdo);
+ else
+ /* DRShit */
+ tdo = TCK_Cycle(aspeed_jtag, 0, tdo);
+
+ dev_err(aspeed_jtag->dev, "%d\n", tdo);
+ sdr->tdio[index] |= (tdo << (shift_bits % 32));
+
+ if ((shift_bits % 32) == 0)
+ dev_err(aspeed_jtag->dev, "R dr->dr_data[%d]: %x\n",
+ index, sdr->tdio[index]);
+ }
+ shift_bits++;
+ remain_xfer--;
+ if ((shift_bits % 32) == 0)
+ index++;
+ }
+
+ TCK_Cycle(aspeed_jtag, 0, 0); /* DRPause */
+
+ if (sdr->enddr == 0) {
+ TCK_Cycle(aspeed_jtag, 1, 0); /* DRExit2 */
+ TCK_Cycle(aspeed_jtag, 1, 0); /* DRUpdate */
+ TCK_Cycle(aspeed_jtag, 0, 0); /* IDLE */
+ }
+ } else {
+ /* hw mode */
+ aspeed_jtag_write(aspeed_jtag, 0, AST_JTAG_SW);
+ while (remain_xfer) {
+ if (sdr->direct) {
+ dev_err(aspeed_jtag->dev,
+ "W dr->dr_data[%d]: %x\n", index,
+ sdr->tdio[index]);
+ aspeed_jtag_write(aspeed_jtag,
+ sdr->tdio[index],
+ AST_JTAG_DATA);
+ } else {
+ aspeed_jtag_write(aspeed_jtag, 0,
+ AST_JTAG_DATA);
+ }
+
+ if (remain_xfer > 32) {
+ shift_bits = 32;
+ /* read bytes were not equals to column length
+ * => Pause-DR
+ */
+ dev_err(aspeed_jtag->dev, "shit bits %d\n",
+ shift_bits);
+ aspeed_jtag_write(aspeed_jtag,
+ JTAG_ENG_EN | JTAG_ENG_OUT_EN |
+ JTAG_DATA_LEN(shift_bits),
+ AST_JTAG_CTRL);
+ aspeed_jtag_write(aspeed_jtag,
+ JTAG_ENG_EN | JTAG_ENG_OUT_EN |
+ JTAG_DATA_LEN(shift_bits) |
+ JTAG_DATA_EN,
+ AST_JTAG_CTRL);
+ aspeed_jtag_wait_data_pause_complete(
+ aspeed_jtag);
+ } else {
+ /* read bytes equals to column length =>
+ * Update-DR
+ */
+ shift_bits = remain_xfer;
+ dev_err(aspeed_jtag->dev,
+ "shit bits %d with last\n",
+ shift_bits);
+ if (sdr->enddr) {
+ dev_err(aspeed_jtag->dev, "DR Keep Pause\n");
+ aspeed_jtag_write(aspeed_jtag,
+ JTAG_ENG_EN | JTAG_ENG_OUT_EN |
+ JTAG_DR_UPDATE |
+ JTAG_DATA_LEN(shift_bits),
+ AST_JTAG_CTRL);
+ aspeed_jtag_write(aspeed_jtag,
+ JTAG_ENG_EN | JTAG_ENG_OUT_EN |
+ JTAG_DR_UPDATE |
+ JTAG_DATA_LEN(shift_bits) |
+ JTAG_DATA_EN, AST_JTAG_CTRL);
+ } else {
+ dev_err(aspeed_jtag->dev, "DR go IDLE\n");
+ aspeed_jtag_write(aspeed_jtag,
+ JTAG_ENG_EN | JTAG_ENG_OUT_EN |
+ JTAG_LAST_DATA |
+ JTAG_DATA_LEN(shift_bits),
+ AST_JTAG_CTRL);
+ aspeed_jtag_write(aspeed_jtag,
+ JTAG_ENG_EN | JTAG_ENG_OUT_EN |
+ JTAG_LAST_DATA |
+ JTAG_DATA_LEN(shift_bits) |
+ JTAG_DATA_EN, AST_JTAG_CTRL);
+ }
+ aspeed_jtag_wait_data_complete(aspeed_jtag);
+ }
+
+ if (!sdr->direct) {
+ if (shift_bits < 32)
+ sdr->tdio[index] =
+ aspeed_jtag_read(aspeed_jtag,
+ AST_JTAG_DATA) >>
+ (32 - shift_bits);
+ else
+ sdr->tdio[index] =
+ aspeed_jtag_read(aspeed_jtag,
+ AST_JTAG_DATA);
+ dev_err(aspeed_jtag->dev,
+ "R dr->dr_data[%d]: %x\n",
+ index, sdr->tdio[index]);
+ }
+
+ remain_xfer = remain_xfer - shift_bits;
+ index++;
+ dev_err(aspeed_jtag->dev, "remain_xfer %d\n",
+ remain_xfer);
+ }
+ aspeed_jtag_write(aspeed_jtag, JTAG_SW_MODE_EN |
+ JTAG_SW_MODE_TDIO, AST_JTAG_SW);
+ }
+
+ aspeed_jtag->sts = sdr->enddr;
+
+ return 0;
+}
+
+static irqreturn_t aspeed_jtag_interrupt(int this_irq, void *dev_id)
+{
+ u32 status;
+ struct aspeed_jtag_info *aspeed_jtag = dev_id;
+
+ status = aspeed_jtag_read(aspeed_jtag, AST_JTAG_ISR);
+ dev_err(aspeed_jtag->dev, "sts %x\n", status);
+
+ if (status & JTAG_INST_COMPLETE) {
+ aspeed_jtag_write(aspeed_jtag, JTAG_INST_COMPLETE |
+ (status & 0xf), AST_JTAG_ISR);
+ aspeed_jtag->flag = JTAG_INST_COMPLETE;
+ }
+
+ if (status & JTAG_DATA_PAUSE) {
+ aspeed_jtag_write(aspeed_jtag, JTAG_DATA_PAUSE |
+ (status & 0xf), AST_JTAG_ISR);
+ aspeed_jtag->flag = JTAG_DATA_PAUSE;
+ }
+
+ if (status & JTAG_DATA_COMPLETE) {
+ aspeed_jtag_write(aspeed_jtag, JTAG_DATA_COMPLETE |
+ (status & 0xf), AST_JTAG_ISR);
+ aspeed_jtag->flag = JTAG_DATA_COMPLETE;
+ }
+
+ if (aspeed_jtag->flag) {
+ wake_up_interruptible(&aspeed_jtag->jtag_wq);
+ return IRQ_HANDLED;
+ } else {
+ dev_err(aspeed_jtag->dev, "aspeed_jtag irq status (%x)\n",
+ status);
+ return IRQ_NONE;
+ }
+}
+
+static long jtag_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct aspeed_jtag_info *aspeed_jtag = file->private_data;
+ void __user *argp = (void __user *)arg;
+ struct sir_xfer sir;
+ struct sdr_xfer sdr;
+ struct runtest_idle run_idle;
+ int ret = 0;
+
+ switch (cmd) {
+ case AST_JTAG_GIOCFREQ:
+ ret = __put_user(aspeed_jtag_get_freq(aspeed_jtag),
+ (unsigned int __user *)arg);
+ break;
+
+ case AST_JTAG_SIOCFREQ:
+ if ((unsigned int )arg > ast_get_pclk())
+ ret = -EFAULT;
+ else
+ aspeed_jtag_set_freq(aspeed_jtag, (unsigned int)arg);
+ break;
+
+ case AST_JTAG_IOCRUNTEST:
+ if (copy_from_user(&run_idle, argp,
+ sizeof(struct runtest_idle)))
+ ret = -EFAULT;
+ else
+ aspeed_jtag_run_test_idle(aspeed_jtag, &run_idle);
+
+ break;
+
+ case AST_JTAG_IOCSIR:
+ if (copy_from_user(&sir, argp, sizeof(struct sir_xfer)))
+ ret = -EFAULT;
+ else
+ aspeed_jtag_sir_xfer(aspeed_jtag, &sir);
+
+ if (copy_to_user(argp, &sir, sizeof(struct sdr_xfer)))
+ ret = -EFAULT;
+
+ break;
+
+ case AST_JTAG_IOCSDR:
+ if (copy_from_user(&sdr, argp, sizeof(struct sdr_xfer)))
+ ret = -EFAULT;
+ else
+ aspeed_jtag_sdr_xfer(aspeed_jtag, &sdr);
+
+ if (copy_to_user(argp, &sdr, sizeof(struct sdr_xfer)))
+ ret = -EFAULT;
+
+ break;
+ default:
+
+ return -ENOTTY;
+ }
+
+ return ret;
+}
+
+static int jtag_open(struct inode *inode, struct file *file)
+{
+ spin_lock(&jtag_state_lock);
+ if (aspeed_jtag->is_open) {
+ spin_unlock(&jtag_state_lock);
+ return -EBUSY;
+ }
+
+ aspeed_jtag->is_open = true;
+ file->private_data = aspeed_jtag;
+ spin_unlock(&jtag_state_lock);
+
+ return 0;
+}
+
+static int jtag_release(struct inode *inode, struct file *file)
+{
+ struct aspeed_jtag_info *drvdata = file->private_data;
+
+ spin_lock(&jtag_state_lock);
+ drvdata->is_open = false;
+ spin_unlock(&jtag_state_lock);
+
+ return 0;
+}
+
+static ssize_t show_sts(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct aspeed_jtag_info *aspeed_jtag = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%s\n", aspeed_jtag->sts ? "Pause" : "Idle");
+}
+
+static DEVICE_ATTR(sts, S_IRUGO, show_sts, NULL);
+
+static ssize_t show_frequency(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct aspeed_jtag_info *aspeed_jtag = dev_get_drvdata(dev);
+
+ return sprintf(buf, "Frequency (%d)\n", ast_get_pclk() /
+ (JTAG_GET_TCK_DIVISOR(aspeed_jtag_read(aspeed_jtag,
+ AST_JTAG_TCK)) + 1));
+}
+
+static ssize_t store_frequency(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ unsigned long input_val;
+ struct aspeed_jtag_info *aspeed_jtag = dev_get_drvdata(dev);
+ int ret;
+
+ ret = kstrtoul(buf, 10, &input_val);
+ if (ret)
+ return ret;
+
+ aspeed_jtag_set_freq(aspeed_jtag, input_val);
+
+ return count;
+}
+
+static DEVICE_ATTR(freq, S_IRUGO | S_IWUSR, show_frequency, store_frequency);
+
+static struct attribute *jtag_sysfs_entries[] = {
+ &dev_attr_freq.attr,
+ &dev_attr_sts.attr,
+ NULL
+};
+
+static struct attribute_group jtag_attribute_group = {
+ .attrs = jtag_sysfs_entries,
+};
+
+static const struct file_operations aspeed_jtag_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = jtag_ioctl,
+ .open = jtag_open,
+ .release = jtag_release,
+};
+
+struct miscdevice aspeed_jtag_misc = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "aspeed-jtag",
+ .fops = &aspeed_jtag_fops,
+};
+
+static void aspeed_jtag_remove_sysfs_group(void *_dev)
+{
+ struct device *dev = _dev;
+
+ sysfs_remove_group(&dev->kobj, &jtag_attribute_group);
+}
+
+
+static int aspeed_jtag_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ int ret = 0;
+
+ if (!of_device_is_compatible(pdev->dev.of_node,
+ "aspeed,aspeed2500-jtag"))
+ return -ENODEV;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "cannot get IORESOURCE_MEM\n");
+ ret = -ENOENT;
+ goto out;
+ }
+
+ aspeed_jtag = devm_kzalloc(&pdev->dev, sizeof(*aspeed_jtag),
+ GFP_KERNEL);
+ if (!aspeed_jtag)
+ return -ENOMEM;
+
+ aspeed_jtag->reg_base = devm_ioremap_resource(&pdev->dev, res);
+ if (!aspeed_jtag->reg_base) {
+ ret = -EIO;
+ goto out_region;
+ }
+
+ aspeed_jtag->irq = platform_get_irq(pdev, 0);
+ if (aspeed_jtag->irq < 0) {
+ dev_err(&pdev->dev, "no irq specified\n");
+ ret = -ENOENT;
+ goto out_region;
+ }
+
+ /* Init JTAG */
+ aspeed_toggle_scu_reset(SCU_RESET_JTAG, 3);
+
+ /* Eanble Clock */
+ aspeed_jtag_write(aspeed_jtag, JTAG_ENG_EN | JTAG_ENG_OUT_EN,
+ AST_JTAG_CTRL);
+ aspeed_jtag_write(aspeed_jtag, JTAG_SW_MODE_EN | JTAG_SW_MODE_TDIO,
+ AST_JTAG_SW);
+
+ ret = devm_request_irq(&pdev->dev, aspeed_jtag->irq,
+ aspeed_jtag_interrupt, 0,
+ "aspeed-jtag", aspeed_jtag);
+ if (ret) {
+ dev_info(&pdev->dev, "aspeed_jtag Unable to get IRQ");
+ goto out_region;
+ }
+
+ aspeed_jtag_write(aspeed_jtag, JTAG_INST_PAUSE | JTAG_INST_COMPLETE |
+ JTAG_DATA_PAUSE | JTAG_DATA_COMPLETE |
+ JTAG_INST_PAUSE_EN | JTAG_INST_COMPLETE_EN |
+ JTAG_DATA_PAUSE_EN | JTAG_DATA_COMPLETE_EN,
+ AST_JTAG_ISR);
+
+ aspeed_jtag->flag = 0;
+ init_waitqueue_head(&aspeed_jtag->jtag_wq);
+
+ ret = misc_register(&aspeed_jtag_misc);
+ if (ret) {
+ dev_err(&pdev->dev, "aspeed_jtag : failed to register misc\n");
+ goto out_region;
+ }
+
+ aspeed_jtag->dev = &pdev->dev;
+ platform_set_drvdata(pdev, aspeed_jtag);
+ dev_set_drvdata(aspeed_jtag_misc.this_device, aspeed_jtag);
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &jtag_attribute_group);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "aspeed_jtag : failed to create sysfs attributes.\n");
+ return -1;
+ }
+ ret = devm_add_action(&pdev->dev, aspeed_jtag_remove_sysfs_group,
+ &pdev->dev);
+ if (ret) {
+ aspeed_jtag_remove_sysfs_group(&pdev->dev);
+ dev_err(&pdev->dev,
+ "Failed to add sysfs cleanup action (%d)\n",
+ ret);
+ return ret;
+ }
+
+ dev_info(&pdev->dev, "aspeed_jtag : driver successfully loaded.\n");
+
+ return 0;
+
+out_region:
+ release_mem_region(res->start, res->end - res->start + 1);
+out:
+ dev_warn(&pdev->dev, "aspeed_jtag : driver init failed ret (%d)\n", ret);
+ return ret;
+}
+
+static int aspeed_jtag_remove(struct platform_device *pdev)
+{
+ misc_deregister(&aspeed_jtag_misc);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int
+aspeed_jtag_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ return 0;
+}
+
+static int
+aspeed_jtag_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+
+#else
+#define aspeed_jtag_suspend NULL
+#define aspeed_jtag_resume NULL
+#endif
+
+static const struct of_device_id aspeed_jtag_of_table[] = {
+ { .compatible = "aspeed,aspeed2500-jtag", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, aspeed_jtag_of_table);
+
+static struct platform_driver aspeed_jtag_driver = {
+ .probe = aspeed_jtag_probe,
+ .remove = aspeed_jtag_remove,
+ .suspend = aspeed_jtag_suspend,
+ .resume = aspeed_jtag_resume,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .owner = THIS_MODULE,
+ .of_match_table = aspeed_jtag_of_table,
+ },
+};
+
+module_platform_driver(aspeed_jtag_driver);
+
+MODULE_AUTHOR("Ryan Chen <ryan_chen at aspeedtech.com>");
+MODULE_DESCRIPTION("AST JTAG LIB Driver");
+MODULE_LICENSE("GPL");
--
2.1.4
More information about the openbmc
mailing list