| From a6c07c0b6f874d548e7573b958db9902323ad3ed Mon Sep 17 00:00:00 2001 | |
| From: David Wang <davidwang@quantatw.com> | |
| Date: Mon, 6 Nov 2023 14:40:09 +0800 | |
| Subject: [PATCH 15/16] drivers: misc: sync npcm jtag-master | |
| --- | |
| drivers/misc/Kconfig | 6 + | |
| drivers/misc/Makefile | 1 + | |
| drivers/misc/npcm8xx-jtag-master.c | 905 +++++++++++++++++++++++++++++ | |
| 3 files changed, 912 insertions(+) | |
| create mode 100644 drivers/misc/npcm8xx-jtag-master.c | |
| diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig | |
| index df50593cf3a9..808f822d97b6 100644 | |
| --- a/drivers/misc/Kconfig | |
| +++ b/drivers/misc/Kconfig | |
| @@ -507,6 +507,12 @@ 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 | |
| + NPCM8xx JTAG Master Interfaces. | |
| + | |
| 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 01cce51d948c..5c29a81e7311 100644 | |
| --- a/drivers/misc/Makefile | |
| +++ b/drivers/misc/Makefile | |
| @@ -64,3 +64,4 @@ obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o | |
| obj-$(CONFIG_NPCM7XX_LPC_BPC) += npcm7xx-lpc-bpc.o | |
| obj-$(CONFIG_NPCM7XX_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..cf1cafec7749 | |
| --- /dev/null | |
| +++ b/drivers/misc/npcm8xx-jtag-master.c | |
| @@ -0,0 +1,905 @@ | |
| +// 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; | |
| +} __attribute__((__packed__)); | |
| + | |
| +struct bitbang_packet { | |
| + struct tck_bitbang *data; | |
| + __u32 length; | |
| +} __attribute__((__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, | |
| +}; | |
| + | |
| +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 u32 npcm_jtm_set_baudrate(struct npcm_jtm *priv, unsigned int speed) | |
| +{ | |
| + u32 ckdiv; | |
| + u32 regtemp; | |
| + u32 freq; | |
| + | |
| + freq = clk_get_rate(priv->clk); | |
| + ckdiv = DIV_ROUND_CLOSEST(freq, (2 * speed)) - 1; | |
| + | |
| + regtemp = readl(priv->base + JTM_CTL); | |
| + regtemp &= ~JTM_CTL_CKDV; | |
| + writel(regtemp | (ckdiv << 16), priv->base + JTM_CTL); | |
| + | |
| + return (freq / ((ckdiv + 1) * 2)); | |
| +} | |
| + | |
| +static void jtag_reset_tapstate(struct npcm_jtm *jtag) | |
| +{ | |
| + u8 tms[2]; | |
| + | |
| + tms[0] = 0xff; | |
| + tms[1] = 0x01; | |
| + npcm_jtm_shift(jtag, NULL, NULL, tms, JTAG_TLR_TMS_COUNT); | |
| + jtag->tapstate = jtagtlr; | |
| +} | |
| + | |
| +static int jtag_set_tapstate(struct npcm_jtm *jtag, | |
| + enum jtagstates from, enum jtagstates to) | |
| +{ | |
| + u8 tms[2]; | |
| + u8 count; | |
| + int ret; | |
| + | |
| + if (from == JTAG_STATE_CURRENT) | |
| + from = jtag->tapstate; | |
| + | |
| + 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) { | |
| + jtag_reset_tapstate(jtag); | |
| + return 0; | |
| + } | |
| + | |
| + tms[0] = tmscyclelookup[from][to].tmsbits; | |
| + count = tmscyclelookup[from][to].count; | |
| + | |
| + if (count == 0) | |
| + return 0; | |
| + | |
| + ret = npcm_jtm_shift(jtag, NULL, NULL, 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; | |
| + | |
| + 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); | |
| + | |
| + /* 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 (jtm_tdo && !ret) | |
| + memcpy(jtm_tdo, jtm_tdi, bytes); | |
| + kfree(jtm_tdi); | |
| + | |
| + return ret; | |
| +} | |
| + | |
| +/* Run in specified state for a specfied number of tcks */ | |
| +static int jtag_run_state(struct npcm_jtm *jtag, enum jtagstates run_state, | |
| + unsigned int tcks) | |
| +{ | |
| + int ret; | |
| + | |
| + jtag_set_tapstate(jtag, JTAG_STATE_CURRENT, run_state); | |
| + jtag->end_tms_high = false; | |
| + ret = npcm_jtm_shift(jtag, NULL, NULL, NULL, tcks); | |
| + | |
| + 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 = npcm_jtm_set_baudrate(priv, value); | |
| + } 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_reset_tapstate(priv); | |
| + jtag_set_tapstate(priv, tapstate.from, | |
| + tapstate.endstate); | |
| + if (tapstate.endstate == JTAG_STATE_CURRENT) | |
| + tapstate.endstate = priv->tapstate; | |
| + if (tapstate.tck && (tapstate.endstate == jtagtlr || | |
| + tapstate.endstate == jtagrti || | |
| + tapstate.endstate == jtagpaudr || | |
| + tapstate.endstate == jtagpauir)) | |
| + jtag_run_state(priv, tapstate.endstate, tapstate.tck); | |
| + 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_run_state(priv, JTAG_STATE_CURRENT, (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); | |
| + | |
| + 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; | |
| + u32 val; | |
| + 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); | |
| + | |
| + priv->freq = npcm_jtm_set_baudrate(priv, NPCM_JTM_DEFAULT_RATE); | |
| + | |
| + /* Deassert TRST for normal operation */ | |
| + val = readl(priv->base + JTM_CTL); | |
| + val |= JTM_CTL_TRST; | |
| + writel(val, priv->base + JTM_CTL); | |
| + | |
| + 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@nuvoton.com>"); | |
| +MODULE_DESCRIPTION("NPCM8xx JTAG Master Driver"); | |
| +MODULE_LICENSE("GPL v2"); | |
| + | |
| -- | |
| 2.25.1 | |