From df47e97141a18be1cd44351893e200c1e36204c5 Mon Sep 17 00:00:00 2001 From: Daniel Goehring Date: Thu, 3 Dec 2020 23:26:42 -0500 Subject: [PATCH] jtag:aspeed: add upstream community JTAG driver Sources: https://github.com/facebook/openbmc-linux/tree/dev-5.6 Signed-off-by: Daniel Goehring --- Documentation/ABI/testing/jtag-dev | 23 + .../devicetree/bindings/jtag/aspeed-jtag.yaml | 71 + Documentation/index.rst | 1 + Documentation/jtag/index.rst | 18 + Documentation/jtag/jtag-summary.rst | 47 + Documentation/jtag/jtagdev.rst | 194 +++ .../userspace-api/ioctl/ioctl-number.rst | 2 + MAINTAINERS | 11 + arch/arm/boot/dts/aspeed-g5.dtsi | 9 + drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/jtag/Kconfig | 38 + drivers/jtag/Makefile | 2 + drivers/jtag/jtag-aspeed.c | 1280 +++++++++++++++++ drivers/jtag/jtag.c | 303 ++++ include/linux/jtag.h | 46 + include/uapi/linux/jtag.h | 194 +++ 17 files changed, 2242 insertions(+) create mode 100644 Documentation/ABI/testing/jtag-dev create mode 100644 Documentation/devicetree/bindings/jtag/aspeed-jtag.yaml create mode 100644 Documentation/jtag/index.rst create mode 100644 Documentation/jtag/jtag-summary.rst create mode 100644 Documentation/jtag/jtagdev.rst create mode 100644 drivers/jtag/Kconfig create mode 100644 drivers/jtag/Makefile create mode 100644 drivers/jtag/jtag-aspeed.c create mode 100644 drivers/jtag/jtag.c create mode 100644 include/linux/jtag.h create mode 100644 include/uapi/linux/jtag.h diff --git a/Documentation/ABI/testing/jtag-dev b/Documentation/ABI/testing/jtag-dev new file mode 100644 index 00000000000000..423baab187614c --- /dev/null +++ b/Documentation/ABI/testing/jtag-dev @@ -0,0 +1,23 @@ +What: /dev/jtag[0-9]+ +Date: July 2018 +KernelVersion: 4.20 +Contact: oleksandrs@mellanox.com +Description: + The misc device files /dev/jtag* are the interface + between JTAG master interface and userspace. + + The ioctl(2)-based ABI is defined and documented in + [include/uapi]. + + The following file operations are supported: + + open(2) + Opens and allocates file descriptor. + + ioctl(2) + Initiate various actions. + See the inline documentation in [include/uapi] + for descriptions of all ioctls. + +Users: + userspace tools which wants to access to JTAG bus diff --git a/Documentation/devicetree/bindings/jtag/aspeed-jtag.yaml b/Documentation/devicetree/bindings/jtag/aspeed-jtag.yaml new file mode 100644 index 00000000000000..3db49b70db457b --- /dev/null +++ b/Documentation/devicetree/bindings/jtag/aspeed-jtag.yaml @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: https://urldefense.proofpoint.com/v2/url?u=http-3A__devicetree.org_schemas_jtag_aspeed-2Djtag.yaml-23&d=DwIBAg&c=5VD0RTtNlTh3ycd41b3MUw&r=iYElT7HC77pRZ3byVvW8ng&m=vGWCsGcOo2XBzrFEoF2nIS4gWviJ18aq1W4UxkRbmXA&s=5LDPQjHKBcLkVCSOxFNx3oLtdKgpW5T1u56Km9Lch5s&e= +$schema: https://urldefense.proofpoint.com/v2/url?u=http-3A__devicetree.org_meta-2Dschemas_core.yaml-23&d=DwIBAg&c=5VD0RTtNlTh3ycd41b3MUw&r=iYElT7HC77pRZ3byVvW8ng&m=vGWCsGcOo2XBzrFEoF2nIS4gWviJ18aq1W4UxkRbmXA&s=r89VV7bcxrO-vDJsqHLKtJVbs-nZVmROiBZlM8YHtiQ&e= + +title: Aspeed JTAG driver for ast2400 and ast2500 SoC + +description: + Driver adds support of Aspeed 2500/2400 series SOC JTAG master controller. + Driver implements the following jtag ops + freq_get + freq_set + status_get + status_set + xfer + mode_set + bitbang + enable + disable + + It has been tested on Mellanox system with BMC equipped with + Aspeed 2520 SoC for programming CPLD devices. + + It has also been tested on Intel system using Aspeed 25xx SoC + for JTAG communication. + +maintainers: + - Oleksandr Shamray + - Jiri Pirko + - Ernesto Corona + +properties: + compatible: + oneOf: + - items: + - enum: + - aspeed,ast2400-jtag + - aspeed,ast2500-jtag + + + reg: + items: + - description: JTAG Master controller register range + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - clocks + +examples: + - | + #include + #include + + jtag: jtag@1e6e4000 { + compatible = "aspeed,ast2500-jtag"; + reg = <0x1e6e4000 0x1c>; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_JTAG_MASTER>; + interrupts = <43>; + }; + +... diff --git a/Documentation/index.rst b/Documentation/index.rst index 71eca31715741f..f8e5849aa5e59e 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -111,6 +111,7 @@ needed). iio/index isdn/index infiniband/index + jtag/index leds/index netlabel/index networking/index diff --git a/Documentation/jtag/index.rst b/Documentation/jtag/index.rst new file mode 100644 index 00000000000000..8a2761d1c17e4e --- /dev/null +++ b/Documentation/jtag/index.rst @@ -0,0 +1,18 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============================== +Joint Test Action Group (JTAG) +============================== + +.. toctree:: + :maxdepth: 1 + + jtag-summary + jtagdev + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/jtag/jtag-summary.rst b/Documentation/jtag/jtag-summary.rst new file mode 100644 index 00000000000000..7d3211be909613 --- /dev/null +++ b/Documentation/jtag/jtag-summary.rst @@ -0,0 +1,47 @@ +.. SPDX-License-Identifier: GPL-2.0 + +==================================== +Linux kernel JTAG support +==================================== + +Introduction to JTAG +==================== + +JTAG is an industry standard for verifying hardware. JTAG provides access to +many logic signals of a complex integrated circuit, including the device pins. + +A JTAG interface is a special interface added to a chip. +Depending on the version of JTAG, two, four, or five pins are added. + +The connector pins are: + * TDI (Test Data In) + * TDO (Test Data Out) + * TCK (Test Clock) + * TMS (Test Mode Select) + * TRST (Test Reset) optional + +JTAG interface is designed to have two parts - basic core driver and +hardware specific driver. The basic driver introduces a general interface +which is not dependent of specific hardware. It provides communication +between user space and hardware specific driver. +Each JTAG device is represented as a char device from (jtag0, jtag1, ...). +Access to a JTAG device is performed through IOCTL calls. + +Call flow example: +:: + + User: open -> /dev/jatgX -> JTAG core driver -> JTAG hardware specific driver + User: ioctl -> /dev/jtagX -> JTAG core driver -> JTAG hardware specific driver + User: close -> /dev/jatgX -> JTAG core driver -> JTAG hardware specific driver + + +THANKS TO +--------- +Contributors to Linux-JTAG discussions include (in alphabetical order, +by last name): + +- Ernesto Corona +- Jiri Pirko +- Oleksandr Shamray +- Steven Filary +- Vadim Pasternak diff --git a/Documentation/jtag/jtagdev.rst b/Documentation/jtag/jtagdev.rst new file mode 100644 index 00000000000000..445f0b9c5b4de0 --- /dev/null +++ b/Documentation/jtag/jtagdev.rst @@ -0,0 +1,194 @@ +.. SPDX-License-Identifier: GPL-2.0 + +================== +JTAG userspace API +================== +JTAG master devices can be accessed through a character misc-device. + +Each JTAG master interface can be accessed by using /dev/jtagN. + +JTAG system calls set: + * SIR (Scan Instruction Register, IEEE 1149.1 Instruction Register scan); + * SDR (Scan Data Register, IEEE 1149.1 Data Register scan); + * RUNTEST (Forces the IEEE 1149.1 bus to a run state for a specified number of clocks. + +open(), close() +--------------- +Open/Close device: +:: + + jtag_fd = open("/dev/jtag0", O_RDWR); + close(jtag_fd); + +ioctl() +------- +All access operations to JTAG devices are performed through ioctl interface. +The IOCTL interface supports these requests: +:: + + JTAG_SIOCSTATE - Force JTAG state machine to go into a TAPC state + JTAG_SIOCFREQ - Set JTAG TCK frequency + JTAG_GIOCFREQ - Get JTAG TCK frequency + JTAG_IOCXFER - send/receive JTAG data Xfer + JTAG_GIOCSTATUS - get current JTAG TAP state + JTAG_SIOCMODE - set JTAG mode flags. + JTAG_IOCBITBANG - JTAG bitbang low level control. + +JTAG_SIOCFREQ +~~~~~~~~~~~~~ +Set JTAG clock speed: +:: + + unsigned int jtag_fd; + ioctl(jtag_fd, JTAG_SIOCFREQ, &frq); + +JTAG_GIOCFREQ +~~~~~~~~~~~~~ +Get JTAG clock speed: +:: + + unsigned int jtag_fd; + ioctl(jtag_fd, JTAG_GIOCFREQ, &frq); + +JTAG_SIOCSTATE +~~~~~~~~~~~~~~ +Force JTAG state machine to go into a TAPC state +:: + + struct jtag_end_tap_state { + __u8 reset; + __u8 endstate; + __u8 tck; + }; + +reset: one of below options +:: + + JTAG_NO_RESET - go through selected endstate from current state + JTAG_FORCE_RESET - go through TEST_LOGIC/RESET state before selected endstate + +endstate: any state listed in jtag_endstate enum +:: + + enum jtag_endstate { + JTAG_STATE_TLRESET, + JTAG_STATE_IDLE, + JTAG_STATE_SELECTDR, + JTAG_STATE_CAPTUREDR, + JTAG_STATE_SHIFTDR, + JTAG_STATE_EXIT1DR, + JTAG_STATE_PAUSEDR, + JTAG_STATE_EXIT2DR, + JTAG_STATE_UPDATEDR, + JTAG_STATE_SELECTIR, + JTAG_STATE_CAPTUREIR, + JTAG_STATE_SHIFTIR, + JTAG_STATE_EXIT1IR, + JTAG_STATE_PAUSEIR, + JTAG_STATE_EXIT2IR, + JTAG_STATE_UPDATEIR + }; + +tck: clock counter + +Example: +:: + + struct jtag_end_tap_state end_state; + + end_state.endstate = JTAG_STATE_IDLE; + end_state.reset = 0; + end_state.tck = data_p->tck; + usleep(25 * 1000); + ioctl(jtag_fd, JTAG_SIOCSTATE, &end_state); + +JTAG_GIOCSTATUS +~~~~~~~~~~~~~~~ +Get JTAG TAPC current machine state +:: + + unsigned int jtag_fd; + jtag_endstate endstate; + ioctl(jtag_fd, JTAG_GIOCSTATUS, &endstate); + +JTAG_IOCXFER +~~~~~~~~~~~~ +Send SDR/SIR transaction +:: + + struct jtag_xfer { + __u8 type; + __u8 direction; + __u8 endstate; + __u8 padding; + __u32 length; + __u64 tdio; + }; + +type: transfer type - JTAG_SIR_XFER/JTAG_SDR_XFER + +direction: xfer direction - JTAG_READ_XFER/JTAG_WRITE_XFER/JTAG_READ_WRITE_XFER + +length: xfer data length in bits + +tdio : xfer data array + +endstate: end state after transaction finish any of jtag_endstate enum + +Example: +:: + + struct jtag_xfer xfer; + static char buf[64]; + static unsigned int buf_len = 0; + [...] + xfer.type = JTAG_SDR_XFER; + xfer.tdio = (__u64)buf; + xfer.length = buf_len; + xfer.endstate = JTAG_STATE_IDLE; + + if (is_read) + xfer.direction = JTAG_READ_XFER; + else if (is_write) + xfer.direction = JTAG_WRITE_XFER; + else + xfer.direction = JTAG_READ_WRITE_XFER; + + ioctl(jtag_fd, JTAG_IOCXFER, &xfer); + +JTAG_SIOCMODE +~~~~~~~~~~~~~ +If hardware driver can support different running modes you can change it. + +Example: +:: + + struct jtag_mode mode; + mode.feature = JTAG_XFER_MODE; + mode.mode = JTAG_XFER_HW_MODE; + ioctl(jtag_fd, JTAG_SIOCMODE, &mode); + +JTAG_IOCBITBANG +~~~~~~~~~~~~~~~ +JTAG Bitbang low level operation. + +Example: +:: + + struct tck_bitbang bitbang + bitbang.tms = 1; + bitbang.tdi = 0; + ioctl(jtag_fd, JTAG_IOCBITBANG, &bitbang); + tdo = bitbang.tdo; + + +THANKS TO +--------- +Contributors to Linux-JTAG discussions include (in alphabetical order, +by last name): + +- Ernesto Corona +- Jiri Pirko +- Oleksandr Shamray +- Steven Filary +- Vadim Pasternak diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index d1d6283479060a..24dfce54c695ad 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -335,6 +335,8 @@ Code Seq# Include File Comments 0xB1 00-1F PPPoX +0xB2 00-0F linux/jtag.h JTAG driver + 0xB3 00 linux/mmc/ioctl.h 0xB4 00-0F linux/gpio.h 0xB5 00-0F uapi/linux/rpmsg.h diff --git a/MAINTAINERS b/MAINTAINERS index a422c614308ec4..a58f8c776dd0e8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9297,6 +9297,17 @@ L: linux-serial@vger.kernel.org S: Orphan F: drivers/tty/serial/jsm/ +JTAG SUBSYSTEM +M: Oleksandr Shamray +M: Vadim Pasternak +M Ernesto Corona +S: Maintained +F: include/linux/jtag.h +F: include/uapi/linux/jtag.h +F: drivers/jtag/ +F: Documentation/devicetree/bindings/jtag/ +F: Documentation/ABI/testing/jtag-dev + K10TEMP HARDWARE MONITORING DRIVER M: Clemens Ladisch L: linux-hwmon@vger.kernel.org diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi index 19288495f41a70..6a451630f164c8 100644 --- a/arch/arm/boot/dts/aspeed-g5.dtsi +++ b/arch/arm/boot/dts/aspeed-g5.dtsi @@ -257,6 +257,15 @@ quality = <100>; }; + jtag: jtag@1e6e4000 { + compatible = "aspeed,ast2500-jtag"; + reg = <0x1e6e4000 0x1c 0x1e6e2004 0x04>; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_JTAG_MASTER>; + interrupts = <43>; + status = "disabled"; + }; + gfx: display@1e6e6000 { compatible = "aspeed,ast2500-gfx", "syscon"; reg = <0x1e6e6000 0x1000>; diff --git a/drivers/Kconfig b/drivers/Kconfig index 1bd21494d4cd84..383848e6ff0b43 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -236,6 +236,8 @@ source "drivers/counter/Kconfig" source "drivers/most/Kconfig" +source "drivers/jtag/Kconfig" + source "drivers/peci/Kconfig" endmenu diff --git a/drivers/Makefile b/drivers/Makefile index bbc6d661b9de79..16dd9174f018c8 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -188,4 +188,5 @@ obj-$(CONFIG_GNSS) += gnss/ obj-$(CONFIG_INTERCONNECT) += interconnect/ obj-$(CONFIG_COUNTER) += counter/ obj-$(CONFIG_MOST) += most/ +obj-$(CONFIG_JTAG) += jtag/ obj-$(CONFIG_PECI) += peci/ diff --git a/drivers/jtag/Kconfig b/drivers/jtag/Kconfig new file mode 100644 index 00000000000000..d3814e57d638f2 --- /dev/null +++ b/drivers/jtag/Kconfig @@ -0,0 +1,38 @@ +menuconfig JTAG + tristate "JTAG support" + help + This provides basic core functionality support for JTAG class devices. + Hardware that is equipped with a JTAG microcontroller can be + supported by using this driver's interfaces. + This driver exposes a set of IOCTLs to the user space for + the following commands: + SDR: Performs an IEEE 1149.1 Data Register scan + SIR: Performs an IEEE 1149.1 Instruction Register scan. + RUNTEST: Forces the IEEE 1149.1 bus to a run state for a specified + number of clocks or a specified time period. + + If you want this support, you should say Y here. + + To compile this driver as a module, choose M here: the module will + be called jtag. + +menuconfig JTAG_ASPEED + tristate "Aspeed SoC JTAG controller support" + depends on JTAG && HAS_IOMEM + depends on ARCH_ASPEED || COMPILE_TEST + help + This provides a support for Aspeed JTAG device, equipped on + Aspeed SoC 24xx and 25xx families. Drivers allows programming + of hardware devices, connected to SoC through the JTAG interface. + + If you want this support, you should say Y here. + + To compile this driver as a module, choose M here: the module will + be called jtag-aspeed. + +config JTAG_ASPEED_LEGACY_UIO + bool "ASPEED JTAG Legacy User Space I/O Support" + depends on JTAG_ASPEED + help + If you say yes here, you get a set of sysfs interfaces and ioctl + commands for supporting legacy user space applications. diff --git a/drivers/jtag/Makefile b/drivers/jtag/Makefile new file mode 100644 index 00000000000000..04a855e2df2830 --- /dev/null +++ b/drivers/jtag/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_JTAG) += jtag.o +obj-$(CONFIG_JTAG_ASPEED) += jtag-aspeed.o diff --git a/drivers/jtag/jtag-aspeed.c b/drivers/jtag/jtag-aspeed.c new file mode 100644 index 00000000000000..e0a46fd12bca7b --- /dev/null +++ b/drivers/jtag/jtag-aspeed.c @@ -0,0 +1,1280 @@ +// SPDX-License-Identifier: GPL-2.0 +// drivers/jtag/aspeed-jtag.c +// +// Copyright (c) 2018 Mellanox Technologies. All rights reserved. +// Copyright (c) 2018 Oleksandr Shamray + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ASPEED_SCU_RESET_JTAG BIT(22) +#define ASPEED_2600_SCU_CLEAR_REGISTER 0x04 + +/* AST2600 JTAG master pins */ +#define ASPEED_2600_SCU_ENABLE_PIN_TDI BIT(4) +#define ASPEED_2600_SCU_ENABLE_PIN_TMS BIT(3) +#define ASPEED_2600_SCU_ENABLE_PIN_TCK BIT(2) +#define ASPEED_2600_SCU_ENABLE_PIN_TDO BIT(1) +#define ASPEED_2600_SCU_ENABLE_PIN_TRSTN BIT(0) + +#define ASPEED_JTAG_DATA 0x00 +#define ASPEED_JTAG_INST 0x04 +#define ASPEED_JTAG_CTRL 0x08 +#define ASPEED_JTAG_ISR 0x0C +#define ASPEED_JTAG_SW 0x10 +#define ASPEED_JTAG_TCK 0x14 +#define ASPEED_JTAG_EC 0x18 + +#define ASPEED_JTAG_DATA_MSB 0x01 +#define ASPEED_JTAG_DATA_CHUNK_SIZE 0x20 + +/* ASPEED_JTAG_CTRL: Engine Control */ +#define ASPEED_JTAG_CTL_ENG_EN BIT(31) +#define ASPEED_JTAG_CTL_ENG_OUT_EN BIT(30) +#define ASPEED_JTAG_CTL_FORCE_TMS BIT(29) +#define ASPEED_JTAG_CTL_IR_UPDATE BIT(26) +#define ASPEED_JTAG_CTL_INST_LEN(x) ((x) << 20) +#define ASPEED_JTAG_CTL_LASPEED_INST BIT(17) +#define ASPEED_JTAG_CTL_INST_EN BIT(16) +#define ASPEED_JTAG_CTL_DR_UPDATE BIT(10) +#define ASPEED_JTAG_CTL_DATA_LEN(x) ((x) << 4) +#define ASPEED_JTAG_CTL_LASPEED_DATA BIT(1) +#define ASPEED_JTAG_CTL_DATA_EN BIT(0) + +/* ASPEED_JTAG_ISR : Interrupt status and enable */ +#define ASPEED_JTAG_ISR_INST_PAUSE BIT(19) +#define ASPEED_JTAG_ISR_INST_COMPLETE BIT(18) +#define ASPEED_JTAG_ISR_DATA_PAUSE BIT(17) +#define ASPEED_JTAG_ISR_DATA_COMPLETE BIT(16) +#define ASPEED_JTAG_ISR_INST_PAUSE_EN BIT(3) +#define ASPEED_JTAG_ISR_INST_COMPLETE_EN BIT(2) +#define ASPEED_JTAG_ISR_DATA_PAUSE_EN BIT(1) +#define ASPEED_JTAG_ISR_DATA_COMPLETE_EN BIT(0) +#define ASPEED_JTAG_ISR_INT_EN_MASK GENMASK(3, 0) +#define ASPEED_JTAG_ISR_INT_MASK GENMASK(19, 16) + +/* ASPEED_JTAG_SW : Software Mode and Status */ +#define ASPEED_JTAG_SW_MODE_EN BIT(19) +#define ASPEED_JTAG_SW_MODE_TCK BIT(18) +#define ASPEED_JTAG_SW_MODE_TMS BIT(17) +#define ASPEED_JTAG_SW_MODE_TDIO BIT(16) + +/* ASPEED_JTAG_TCK : TCK Control */ +#define ASPEED_JTAG_TCK_DIVISOR_MASK GENMASK(10, 0) +#define ASPEED_JTAG_TCK_GET_DIV(x) ((x) & ASPEED_JTAG_TCK_DIVISOR_MASK) + +/* ASPEED_JTAG_EC : Controller set for go to IDLE */ +#define ASPEED_JTAG_EC_GO_IDLE BIT(0) + +#define ASPEED_JTAG_IOUT_LEN(len) \ + (ASPEED_JTAG_CTL_ENG_EN | \ + ASPEED_JTAG_CTL_ENG_OUT_EN | \ + ASPEED_JTAG_CTL_INST_LEN(len)) + +#define ASPEED_JTAG_DOUT_LEN(len) \ + (ASPEED_JTAG_CTL_ENG_EN | \ + ASPEED_JTAG_CTL_ENG_OUT_EN | \ + ASPEED_JTAG_CTL_DATA_LEN(len)) + +#define ASPEED_JTAG_SW_TDIO (ASPEED_JTAG_SW_MODE_EN | ASPEED_JTAG_SW_MODE_TDIO) + +#define ASPEED_JTAG_GET_TDI(direction, byte) \ + (((direction) & JTAG_WRITE_XFER) ? byte : UINT_MAX) + +#define ASPEED_JTAG_TCK_WAIT 10 +#define ASPEED_JTAG_RESET_CNTR 10 +#define WAIT_ITERATIONS 75 + +/*#define USE_INTERRUPTS*/ + +static const char * const regnames[] = { + [ASPEED_JTAG_DATA] = "ASPEED_JTAG_DATA", + [ASPEED_JTAG_INST] = "ASPEED_JTAG_INST", + [ASPEED_JTAG_CTRL] = "ASPEED_JTAG_CTRL", + [ASPEED_JTAG_ISR] = "ASPEED_JTAG_ISR", + [ASPEED_JTAG_SW] = "ASPEED_JTAG_SW", + [ASPEED_JTAG_TCK] = "ASPEED_JTAG_TCK", + [ASPEED_JTAG_EC] = "ASPEED_JTAG_EC", +}; + +#define ASPEED_JTAG_NAME "jtag-aspeed" + +struct aspeed_jtag { + void __iomem *reg_base; + void __iomem *scu_base; + void __iomem *scupin_ctrl; + struct device *dev; + struct clk *pclk; + enum jtag_endstate status; + int irq; + struct reset_control *rst; + u32 flag; + wait_queue_head_t jtag_wq; + u32 mode; + int scu_clear_reg; +}; + +/* + * This structure represents a TMS cycle, as expressed in a set of bits and a + * count of bits (note: there are no start->end state transitions that require + * more than 1 byte of TMS cycles) + */ +struct tms_cycle { + 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. + */ +static const struct tms_cycle _tms_cycle_lookup[][16] = { +/* TLR RTI SelDR CapDR SDR Ex1DR*/ +/* TLR */{{0x00, 0}, {0x00, 1}, {0x02, 2}, {0x02, 3}, {0x02, 4}, {0x0a, 4}, +/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ + {0x0a, 5}, {0x2a, 6}, {0x1a, 5}, {0x06, 3}, {0x06, 4}, {0x06, 5}, +/* Ex1IR PIR Ex2IR UpdIR*/ + {0x16, 5}, {0x16, 6}, {0x56, 7}, {0x36, 6} }, + +/* TLR RTI SelDR CapDR SDR Ex1DR*/ +/* RTI */{{0x07, 3}, {0x00, 0}, {0x01, 1}, {0x01, 2}, {0x01, 3}, {0x05, 3}, +/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ + {0x05, 4}, {0x15, 5}, {0x0d, 4}, {0x03, 2}, {0x03, 3}, {0x03, 4}, +/* Ex1IR PIR Ex2IR UpdIR*/ + {0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x1b, 5} }, + +/* TLR RTI SelDR CapDR SDR Ex1DR*/ +/* SelDR*/{{0x03, 2}, {0x03, 3}, {0x00, 0}, {0x00, 1}, {0x00, 2}, {0x02, 2}, +/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ + {0x02, 3}, {0x0a, 4}, {0x06, 3}, {0x01, 1}, {0x01, 2}, {0x01, 3}, +/* Ex1IR PIR Ex2IR UpdIR*/ + {0x05, 3}, {0x05, 4}, {0x15, 5}, {0x0d, 4} }, + +/* TLR RTI SelDR CapDR SDR Ex1DR*/ +/* CapDR*/{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x00, 0}, {0x00, 1}, {0x01, 1}, +/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ + {0x01, 2}, {0x05, 3}, {0x03, 2}, {0x0f, 4}, {0x0f, 5}, {0x0f, 6}, +/* Ex1IR PIR Ex2IR UpdIR*/ + {0x2f, 6}, {0x2f, 7}, {0xaf, 8}, {0x6f, 7} }, + +/* TLR RTI SelDR CapDR SDR Ex1DR*/ +/* SDR */{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x00, 0}, {0x01, 1}, +/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ + {0x01, 2}, {0x05, 3}, {0x03, 2}, {0x0f, 4}, {0x0f, 5}, {0x0f, 6}, +/* Ex1IR PIR Ex2IR UpdIR*/ + {0x2f, 6}, {0x2f, 7}, {0xaf, 8}, {0x6f, 7} }, + +/* TLR RTI SelDR CapDR SDR Ex1DR*/ +/* Ex1DR*/{{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x02, 3}, {0x00, 0}, +/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ + {0x00, 1}, {0x02, 2}, {0x01, 1}, {0x07, 3}, {0x07, 4}, {0x07, 5}, +/* Ex1IR PIR Ex2IR UpdIR*/ + {0x17, 5}, {0x17, 6}, {0x57, 7}, {0x37, 6} }, + +/* TLR RTI SelDR CapDR SDR Ex1DR*/ +/* PDR */{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x01, 2}, {0x05, 3}, +/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ + {0x00, 0}, {0x01, 1}, {0x03, 2}, {0x0f, 4}, {0x0f, 5}, {0x0f, 6}, +/* Ex1IR PIR Ex2IR UpdIR*/ + {0x2f, 6}, {0x2f, 7}, {0xaf, 8}, {0x6f, 7} }, + +/* TLR RTI SelDR CapDR SDR Ex1DR*/ +/* Ex2DR*/{{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x00, 1}, {0x02, 2}, +/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ + {0x02, 3}, {0x00, 0}, {0x01, 1}, {0x07, 3}, {0x07, 4}, {0x07, 5}, +/* Ex1IR PIR Ex2IR UpdIR*/ + {0x17, 5}, {0x17, 6}, {0x57, 7}, {0x37, 6} }, + +/* TLR RTI SelDR CapDR SDR Ex1DR*/ +/* UpdDR*/{{0x07, 3}, {0x00, 1}, {0x01, 1}, {0x01, 2}, {0x01, 3}, {0x05, 3}, +/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ + {0x05, 4}, {0x15, 5}, {0x00, 0}, {0x03, 2}, {0x03, 3}, {0x03, 4}, +/* Ex1IR PIR Ex2IR UpdIR*/ + {0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x1b, 5} }, + +/* TLR RTI SelDR CapDR SDR Ex1DR*/ +/* SelIR*/{{0x01, 1}, {0x01, 2}, {0x05, 3}, {0x05, 4}, {0x05, 5}, {0x15, 5}, +/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ + {0x15, 6}, {0x55, 7}, {0x35, 6}, {0x00, 0}, {0x00, 1}, {0x00, 2}, +/* Ex1IR PIR Ex2IR UpdIR*/ + {0x02, 2}, {0x02, 3}, {0x0a, 4}, {0x06, 3} }, + +/* TLR RTI SelDR CapDR SDR Ex1DR*/ +/* CapIR*/{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5}, {0x17, 5}, +/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ + {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4}, {0x00, 0}, {0x00, 1}, +/* Ex1IR PIR Ex2IR UpdIR*/ + {0x01, 1}, {0x01, 2}, {0x05, 3}, {0x03, 2} }, + +/* TLR RTI SelDR CapDR SDR Ex1DR*/ +/* SIR */{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5}, {0x17, 5}, +/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ + {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4}, {0x0f, 5}, {0x00, 0}, +/* Ex1IR PIR Ex2IR UpdIR*/ + {0x01, 1}, {0x01, 2}, {0x05, 3}, {0x03, 2} }, + +/* TLR RTI SelDR CapDR SDR Ex1DR*/ +/* Ex1IR*/{{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x03, 4}, {0x0b, 4}, +/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ + {0x0b, 5}, {0x2b, 6}, {0x1b, 5}, {0x07, 3}, {0x07, 4}, {0x02, 3}, +/* Ex1IR PIR Ex2IR UpdIR*/ + {0x00, 0}, {0x00, 1}, {0x02, 2}, {0x01, 1} }, + +/* TLR RTI SelDR CapDR SDR Ex1DR*/ +/* PIR */{{0x1f, 5}, {0x03, 3}, {0x07, 3}, {0x07, 4}, {0x07, 5}, {0x17, 5}, +/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ + {0x17, 6}, {0x57, 7}, {0x37, 6}, {0x0f, 4}, {0x0f, 5}, {0x01, 2}, +/* Ex1IR PIR Ex2IR UpdIR*/ + {0x05, 3}, {0x00, 0}, {0x01, 1}, {0x03, 2} }, + +/* TLR RTI SelDR CapDR SDR Ex1DR*/ +/* Ex2IR*/{{0x0f, 4}, {0x01, 2}, {0x03, 2}, {0x03, 3}, {0x03, 4}, {0x0b, 4}, +/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ + {0x0b, 5}, {0x2b, 6}, {0x1b, 5}, {0x07, 3}, {0x07, 4}, {0x00, 1}, +/* Ex1IR PIR Ex2IR UpdIR*/ + {0x02, 2}, {0x02, 3}, {0x00, 0}, {0x01, 1} }, + +/* TLR RTI SelDR CapDR SDR Ex1DR*/ +/* UpdIR*/{{0x07, 3}, {0x00, 1}, {0x01, 1}, {0x01, 2}, {0x01, 3}, {0x05, 3}, +/* PDR Ex2DR UpdDR SelIR CapIR SIR*/ + {0x05, 4}, {0x15, 5}, {0x0d, 4}, {0x03, 2}, {0x03, 3}, {0x03, 4}, +/* Ex1IR PIR Ex2IR UpdIR*/ + {0x0b, 4}, {0x0b, 5}, {0x2b, 6}, {0x00, 0} }, +}; + +static char *end_status_str[] = { + "tlr", "idle", "selDR", "capDR", "sDR", "ex1DR", "pDR", "ex2DR", + "updDR", "selIR", "capIR", "sIR", "ex1IR", "pIR", "ex2IR", "updIR" +}; + +static u32 aspeed_jtag_read(struct aspeed_jtag *aspeed_jtag, u32 reg) +{ + u32 val = readl(aspeed_jtag->reg_base + reg); + + dev_dbg(aspeed_jtag->dev, "read:%s val = 0x%08x\n", regnames[reg], val); + return val; +} + +static void +aspeed_jtag_write(struct aspeed_jtag *aspeed_jtag, u32 val, u32 reg) +{ + dev_dbg(aspeed_jtag->dev, "write:%s val = 0x%08x\n", + regnames[reg], val); + writel(val, aspeed_jtag->reg_base + reg); +} + +static int aspeed_jtag_freq_set(struct jtag *jtag, u32 freq) +{ + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + unsigned long apb_frq; + u32 tck_val; + u16 div; + + apb_frq = clk_get_rate(aspeed_jtag->pclk); + if (!apb_frq) + return -ENOTSUPP; + + div = (apb_frq - 1) / freq; + tck_val = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_TCK); + aspeed_jtag_write(aspeed_jtag, + (tck_val & ~ASPEED_JTAG_TCK_DIVISOR_MASK) | div, + ASPEED_JTAG_TCK); + return 0; +} + +static int aspeed_jtag_freq_get(struct jtag *jtag, u32 *frq) +{ + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + u32 pclk; + u32 tck; + + pclk = clk_get_rate(aspeed_jtag->pclk); + tck = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_TCK); + *frq = pclk / (ASPEED_JTAG_TCK_GET_DIV(tck) + 1); + + return 0; +} + +static inline void aspeed_jtag_slave(struct aspeed_jtag *aspeed_jtag) +{ + u32 scu_reg; + if (aspeed_jtag->scu_clear_reg) { + writel(ASPEED_SCU_RESET_JTAG, aspeed_jtag->scu_base); + } else { + scu_reg = readl(aspeed_jtag->scu_base); + writel(scu_reg | ASPEED_SCU_RESET_JTAG, aspeed_jtag->scu_base); + } +} + +static inline void aspeed_jtag_master(struct aspeed_jtag *aspeed_jtag) +{ + u32 scu_reg; + u32 val; + if (aspeed_jtag->scu_clear_reg) { + writel(ASPEED_SCU_RESET_JTAG, + aspeed_jtag->scu_base + ASPEED_2600_SCU_CLEAR_REGISTER); + } else { + scu_reg = readl(aspeed_jtag->scu_base); + writel(scu_reg & ~ASPEED_SCU_RESET_JTAG, aspeed_jtag->scu_base); + } + + if (aspeed_jtag->scupin_ctrl) { + val = readl(aspeed_jtag->scupin_ctrl); + writel((val | + ASPEED_2600_SCU_ENABLE_PIN_TDI | + ASPEED_2600_SCU_ENABLE_PIN_TMS | + ASPEED_2600_SCU_ENABLE_PIN_TCK | + ASPEED_2600_SCU_ENABLE_PIN_TDO) & + ~ASPEED_2600_SCU_ENABLE_PIN_TRSTN, + aspeed_jtag->scupin_ctrl); + } + + aspeed_jtag_write(aspeed_jtag, (ASPEED_JTAG_CTL_ENG_EN | + ASPEED_JTAG_CTL_ENG_OUT_EN), + ASPEED_JTAG_CTRL); + + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_MODE_EN | + ASPEED_JTAG_SW_MODE_TDIO, + ASPEED_JTAG_SW); + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_ISR_INST_PAUSE | + ASPEED_JTAG_ISR_INST_COMPLETE | + ASPEED_JTAG_ISR_DATA_PAUSE | + ASPEED_JTAG_ISR_DATA_COMPLETE | + ASPEED_JTAG_ISR_INST_PAUSE_EN | + ASPEED_JTAG_ISR_INST_COMPLETE_EN | + ASPEED_JTAG_ISR_DATA_PAUSE_EN | + ASPEED_JTAG_ISR_DATA_COMPLETE_EN, + ASPEED_JTAG_ISR); /* Enable Interrupt */ +} + +static int aspeed_jtag_mode_set(struct jtag *jtag, struct jtag_mode *jtag_mode) +{ + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + + switch (jtag_mode->feature) { + case JTAG_XFER_MODE: + aspeed_jtag->mode = jtag_mode->mode; + break; + case JTAG_CONTROL_MODE: + if (jtag_mode->mode == JTAG_SLAVE_MODE) + aspeed_jtag_slave(aspeed_jtag); + else if (jtag_mode->mode == JTAG_MASTER_MODE) + aspeed_jtag_master(aspeed_jtag); + break; + default: + return -EINVAL; + } + return 0; +} + +static char aspeed_jtag_tck_cycle(struct aspeed_jtag *aspeed_jtag, + u8 tms, u8 tdi) +{ + char tdo = 0; + + /* TCK = 0 */ + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_MODE_EN | + (tms * ASPEED_JTAG_SW_MODE_TMS) | + (tdi * ASPEED_JTAG_SW_MODE_TDIO), ASPEED_JTAG_SW); + + aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_SW); + + /* TCK = 1 */ + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_MODE_EN | + ASPEED_JTAG_SW_MODE_TCK | + (tms * ASPEED_JTAG_SW_MODE_TMS) | + (tdi * ASPEED_JTAG_SW_MODE_TDIO), ASPEED_JTAG_SW); + + if (aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_SW) & + ASPEED_JTAG_SW_MODE_TDIO) + tdo = 1; + + return tdo; +} + +static int aspeed_jtag_bitbang(struct jtag *jtag, + struct tck_bitbang *tck_bitbang) +{ + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + + tck_bitbang->tdo = aspeed_jtag_tck_cycle(aspeed_jtag, + tck_bitbang->tms, + tck_bitbang->tdi); + return 0; +} + +static int aspeed_jtag_wait_instruction_pause(struct aspeed_jtag *aspeed_jtag) +{ + int res = 0; +#ifdef USE_INTERRUPTS + res = wait_event_interruptible(aspeed_jtag->jtag_wq, + aspeed_jtag->flag & + ASPEED_JTAG_ISR_INST_PAUSE); + aspeed_jtag->flag &= ~ASPEED_JTAG_ISR_INST_PAUSE; +#else + u32 status = 0; + u32 iterations = 0; + + while ((status & ASPEED_JTAG_ISR_INST_PAUSE) == 0) { + status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR); + dev_dbg(aspeed_jtag->dev, "%s = 0x%08x\n", __func__, status); + iterations++; + if (iterations > WAIT_ITERATIONS) { + dev_err(aspeed_jtag->dev, + "aspeed_jtag driver timed out waiting for instruction pause complete\n"); + res = -EFAULT; + break; + } + if ((status & ASPEED_JTAG_ISR_DATA_COMPLETE) == 0) { + if (iterations % 25 == 0) + usleep_range(1, 5); + else + udelay(1); + } + } + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_ISR_INST_PAUSE | + (status & 0xf), + ASPEED_JTAG_ISR); +#endif + return res; +} + +static int +aspeed_jtag_wait_instruction_complete(struct aspeed_jtag *aspeed_jtag) +{ + int res = 0; +#ifdef USE_INTERRUPTS + res = wait_event_interruptible(aspeed_jtag->jtag_wq, + aspeed_jtag->flag & + ASPEED_JTAG_ISR_INST_COMPLETE); + aspeed_jtag->flag &= ~ASPEED_JTAG_ISR_INST_COMPLETE; +#else + u32 status = 0; + u32 iterations = 0; + + while ((status & ASPEED_JTAG_ISR_INST_COMPLETE) == 0) { + status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR); + dev_dbg(aspeed_jtag->dev, "%s = 0x%08x\n", __func__, status); + iterations++; + if (iterations > WAIT_ITERATIONS) { + dev_err(aspeed_jtag->dev, + "aspeed_jtag driver timed out waiting for instruction complete\n"); + res = -EFAULT; + break; + } + if ((status & ASPEED_JTAG_ISR_DATA_COMPLETE) == 0) { + if (iterations % 25 == 0) + usleep_range(1, 5); + else + udelay(1); + } + } + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_ISR_INST_COMPLETE | + (status & 0xf), + ASPEED_JTAG_ISR); +#endif + return res; +} + +static int +aspeed_jtag_wait_data_pause_complete(struct aspeed_jtag *aspeed_jtag) +{ + int res = 0; +#ifdef USE_INTERRUPTS + res = wait_event_interruptible(aspeed_jtag->jtag_wq, + aspeed_jtag->flag & + ASPEED_JTAG_ISR_DATA_PAUSE); + aspeed_jtag->flag &= ~ASPEED_JTAG_ISR_DATA_PAUSE; +#else + u32 status = 0; + u32 iterations = 0; + + while ((status & ASPEED_JTAG_ISR_DATA_PAUSE) == 0) { + status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR); + dev_dbg(aspeed_jtag->dev, "%s = 0x%08x\n", __func__, status); + iterations++; + if (iterations > WAIT_ITERATIONS) { + dev_err(aspeed_jtag->dev, + "aspeed_jtag driver timed out waiting for data pause complete\n"); + res = -EFAULT; + break; + } + if ((status & ASPEED_JTAG_ISR_DATA_COMPLETE) == 0) { + if (iterations % 25 == 0) + usleep_range(1, 5); + else + udelay(1); + } + } + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_ISR_DATA_PAUSE | + (status & 0xf), ASPEED_JTAG_ISR); +#endif + return res; +} + +static int aspeed_jtag_wait_data_complete(struct aspeed_jtag *aspeed_jtag) +{ + int res = 0; +#ifdef USE_INTERRUPTS + res = wait_event_interruptible(aspeed_jtag->jtag_wq, + aspeed_jtag->flag & + ASPEED_JTAG_ISR_DATA_COMPLETE); + aspeed_jtag->flag &= ~ASPEED_JTAG_ISR_DATA_COMPLETE; +#else + u32 status = 0; + u32 iterations = 0; + + while ((status & ASPEED_JTAG_ISR_DATA_COMPLETE) == 0) { + status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR); + dev_dbg(aspeed_jtag->dev, "%s = 0x%08x\n", __func__, status); + iterations++; + if (iterations > WAIT_ITERATIONS) { + dev_err(aspeed_jtag->dev, + "ast_jtag driver timed out waiting for data complete\n"); + res = -EFAULT; + break; + } + if ((status & ASPEED_JTAG_ISR_DATA_COMPLETE) == 0) { + if (iterations % 25 == 0) + usleep_range(1, 5); + else + udelay(1); + } + } + aspeed_jtag_write(aspeed_jtag, + ASPEED_JTAG_ISR_DATA_COMPLETE | (status & 0xf), + ASPEED_JTAG_ISR); +#endif + return res; +} + +static void aspeed_jtag_set_tap_state(struct aspeed_jtag *aspeed_jtag, + enum jtag_endstate endstate) +{ + int i = 0; + enum jtag_endstate from, to; + + from = aspeed_jtag->status; + to = endstate; + for (i = 0; i < _tms_cycle_lookup[from][to].count; i++) + aspeed_jtag_tck_cycle(aspeed_jtag, + ((_tms_cycle_lookup[from][to].tmsbits >> i) & 0x1), 0); + aspeed_jtag->status = endstate; +} + +static void aspeed_jtag_end_tap_state_sw(struct aspeed_jtag *aspeed_jtag, + struct jtag_end_tap_state *endstate) +{ + /* SW mode from curent tap state -> to end_state */ + if (endstate->reset) { + int i = 0; + + for (i = 0; i < ASPEED_JTAG_RESET_CNTR; i++) + aspeed_jtag_tck_cycle(aspeed_jtag, 1, 0); + aspeed_jtag->status = JTAG_STATE_TLRESET; + } + + aspeed_jtag_set_tap_state(aspeed_jtag, endstate->endstate); +} + +static int aspeed_jtag_status_set(struct jtag *jtag, + struct jtag_end_tap_state *endstate) +{ + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + + dev_dbg(aspeed_jtag->dev, "Set TAP state: %s\n", + end_status_str[endstate->endstate]); + + if (!(aspeed_jtag->mode & JTAG_XFER_HW_MODE)) { + aspeed_jtag_end_tap_state_sw(aspeed_jtag, endstate); + return 0; + } + + /* x TMS high + 1 TMS low */ + if (endstate->reset) { + /* Disable sw mode */ + aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); + mdelay(1); + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_CTL_ENG_EN | + ASPEED_JTAG_CTL_ENG_OUT_EN | + ASPEED_JTAG_CTL_FORCE_TMS, ASPEED_JTAG_CTRL); + mdelay(1); + aspeed_jtag_write(aspeed_jtag, + ASPEED_JTAG_SW_TDIO, ASPEED_JTAG_SW); + aspeed_jtag->status = JTAG_STATE_TLRESET; + } + + return 0; +} + +static void aspeed_jtag_xfer_sw(struct aspeed_jtag *aspeed_jtag, + struct jtag_xfer *xfer, u32 *data) +{ + unsigned long remain_xfer = xfer->length; + unsigned long shift_bits = 0; + unsigned long index = 0; + unsigned long tdi; + char tdo; + + dev_dbg(aspeed_jtag->dev, "SW JTAG SHIFT %s, length = %d\n", + (xfer->type == JTAG_SIR_XFER) ? "IR" : "DR", xfer->length); + + if (xfer->type == JTAG_SIR_XFER) + aspeed_jtag_set_tap_state(aspeed_jtag, JTAG_STATE_SHIFTIR); + else + aspeed_jtag_set_tap_state(aspeed_jtag, JTAG_STATE_SHIFTDR); + + tdi = ASPEED_JTAG_GET_TDI(xfer->direction, data[index]); + data[index] = 0; + while (remain_xfer > 1) { + tdo = aspeed_jtag_tck_cycle(aspeed_jtag, 0, + tdi & ASPEED_JTAG_DATA_MSB); + data[index] |= tdo << (shift_bits % + ASPEED_JTAG_DATA_CHUNK_SIZE); + tdi >>= 1; + shift_bits++; + remain_xfer--; + + if (shift_bits % ASPEED_JTAG_DATA_CHUNK_SIZE == 0) { + tdo = 0; + index++; + tdi = ASPEED_JTAG_GET_TDI(xfer->direction, data[index]); + data[index] = 0; + } + } + + if ((xfer->endstate == (xfer->type == JTAG_SIR_XFER ? + JTAG_STATE_SHIFTIR : JTAG_STATE_SHIFTDR))) { + /* Stay in Shift IR/DR*/ + tdo = aspeed_jtag_tck_cycle(aspeed_jtag, 0, + tdi & ASPEED_JTAG_DATA_MSB); + data[index] |= tdo << (shift_bits % + ASPEED_JTAG_DATA_CHUNK_SIZE); + } else { + /* Goto end state */ + tdo = aspeed_jtag_tck_cycle(aspeed_jtag, 1, + tdi & ASPEED_JTAG_DATA_MSB); + data[index] |= tdo << (shift_bits % + ASPEED_JTAG_DATA_CHUNK_SIZE); + aspeed_jtag->status = (xfer->type == JTAG_SIR_XFER) ? + JTAG_STATE_EXIT1IR : JTAG_STATE_EXIT1DR; + aspeed_jtag_set_tap_state(aspeed_jtag, xfer->endstate); + } +} + +static int aspeed_jtag_xfer_push_data(struct aspeed_jtag *aspeed_jtag, + enum jtag_xfer_type type, u32 bits_len) +{ + int res = 0; + + if (type == JTAG_SIR_XFER) { + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_IOUT_LEN(bits_len), + ASPEED_JTAG_CTRL); + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_IOUT_LEN(bits_len) | + ASPEED_JTAG_CTL_INST_EN, ASPEED_JTAG_CTRL); + res = aspeed_jtag_wait_instruction_pause(aspeed_jtag); + } else { + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_DOUT_LEN(bits_len), + ASPEED_JTAG_CTRL); + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_DOUT_LEN(bits_len) | + ASPEED_JTAG_CTL_DATA_EN, ASPEED_JTAG_CTRL); + res = aspeed_jtag_wait_data_pause_complete(aspeed_jtag); + } + return res; +} + +static int aspeed_jtag_xfer_push_data_last(struct aspeed_jtag *aspeed_jtag, + enum jtag_xfer_type type, + u32 shift_bits, + enum jtag_endstate endstate) +{ + int res = 0; + + if (type == JTAG_SIR_XFER) { + aspeed_jtag_write(aspeed_jtag, + ASPEED_JTAG_IOUT_LEN(shift_bits) | + ASPEED_JTAG_CTL_LASPEED_INST, + ASPEED_JTAG_CTRL); + aspeed_jtag_write(aspeed_jtag, + ASPEED_JTAG_IOUT_LEN(shift_bits) | + ASPEED_JTAG_CTL_LASPEED_INST | + ASPEED_JTAG_CTL_INST_EN, + ASPEED_JTAG_CTRL); + res = aspeed_jtag_wait_instruction_complete(aspeed_jtag); + } else { + aspeed_jtag_write(aspeed_jtag, + ASPEED_JTAG_DOUT_LEN(shift_bits) | + ASPEED_JTAG_CTL_LASPEED_DATA, + ASPEED_JTAG_CTRL); + aspeed_jtag_write(aspeed_jtag, + ASPEED_JTAG_DOUT_LEN(shift_bits) | + ASPEED_JTAG_CTL_LASPEED_DATA | + ASPEED_JTAG_CTL_DATA_EN, + ASPEED_JTAG_CTRL); + res = aspeed_jtag_wait_data_complete(aspeed_jtag); + } + return res; +} + +static int aspeed_jtag_xfer_hw(struct aspeed_jtag *aspeed_jtag, + struct jtag_xfer *xfer, u32 *data) +{ + unsigned long remain_xfer = xfer->length; + unsigned long index = 0; + char shift_bits; + u32 data_reg; + u32 scan_end; + + dev_dbg(aspeed_jtag->dev, "HW JTAG SHIFT %s, length = %d\n", + (xfer->type == JTAG_SIR_XFER) ? "IR" : "DR", xfer->length); + data_reg = xfer->type == JTAG_SIR_XFER ? + ASPEED_JTAG_INST : ASPEED_JTAG_DATA; + if (xfer->endstate == JTAG_STATE_SHIFTIR || + xfer->endstate == JTAG_STATE_SHIFTDR || + xfer->endstate == JTAG_STATE_PAUSEIR || + xfer->endstate == JTAG_STATE_PAUSEDR) { + scan_end = 0; + } else { + scan_end = 1; + } + + while (remain_xfer) { + if (xfer->direction & JTAG_WRITE_XFER) + aspeed_jtag_write(aspeed_jtag, data[index], data_reg); + else + aspeed_jtag_write(aspeed_jtag, 0, data_reg); + + if (remain_xfer > ASPEED_JTAG_DATA_CHUNK_SIZE) { + dev_dbg(aspeed_jtag->dev, + "Chunk len=%d chunk_size=%d remain_xfer=%lu\n", + xfer->length, ASPEED_JTAG_DATA_CHUNK_SIZE, + remain_xfer); + shift_bits = ASPEED_JTAG_DATA_CHUNK_SIZE; + + /* + * Read bytes were not equals to column length + * and continue in Shift IR/DR + */ + if (aspeed_jtag_xfer_push_data(aspeed_jtag, xfer->type, + shift_bits) != 0) { + return -EFAULT; + } + } else { + /* + * Read bytes equals to column length + */ + shift_bits = remain_xfer; + if (scan_end) { + /* + * If this data is the end of the transmission + * send remaining bits and go to endstate + */ + dev_dbg(aspeed_jtag->dev, + "Last len=%d chunk_size=%d remain_xfer=%lu\n", + xfer->length, + ASPEED_JTAG_DATA_CHUNK_SIZE, + remain_xfer); + if (aspeed_jtag_xfer_push_data_last( + aspeed_jtag, + xfer->type, + shift_bits, + xfer->endstate) != 0) { + return -EFAULT; + } + } else { + /* + * If transmission is waiting for additional + * data send remaining bits and stay in + * SHIFT IR/DR + */ + dev_dbg(aspeed_jtag->dev, + "Tail len=%d chunk_size=%d remain_xfer=%lu\n", + xfer->length, + ASPEED_JTAG_DATA_CHUNK_SIZE, + remain_xfer); + if (aspeed_jtag_xfer_push_data(aspeed_jtag, + xfer->type, + shift_bits) + != 0) { + return -EFAULT; + } + } + } + + if (xfer->direction & JTAG_READ_XFER) { + if (shift_bits < ASPEED_JTAG_DATA_CHUNK_SIZE) { + data[index] = aspeed_jtag_read(aspeed_jtag, + data_reg); + + data[index] >>= ASPEED_JTAG_DATA_CHUNK_SIZE - + shift_bits; + } else { + data[index] = aspeed_jtag_read(aspeed_jtag, + data_reg); + } + } + + remain_xfer = remain_xfer - shift_bits; + index++; + } + return 0; +} + +static int aspeed_jtag_xfer(struct jtag *jtag, struct jtag_xfer *xfer, + u8 *xfer_data) +{ + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + + if (!(aspeed_jtag->mode & JTAG_XFER_HW_MODE)) { + /* SW mode */ + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_TDIO, + ASPEED_JTAG_SW); + + aspeed_jtag_xfer_sw(aspeed_jtag, xfer, (u32 *)xfer_data); + } else { + /* HW mode */ + aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); + if (aspeed_jtag_xfer_hw(aspeed_jtag, xfer, + (u32 *)xfer_data) != 0) + return -EFAULT; + } + + aspeed_jtag->status = xfer->endstate; + return 0; +} + +static int aspeed_jtag_status_get(struct jtag *jtag, u32 *status) +{ + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + + *status = aspeed_jtag->status; + return 0; +} + +#ifdef USE_INTERRUPTS +static irqreturn_t aspeed_jtag_interrupt(s32 this_irq, void *dev_id) +{ + struct aspeed_jtag *aspeed_jtag = dev_id; + irqreturn_t ret = IRQ_HANDLED; + u32 status; + + status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR); + + if (status & ASPEED_JTAG_ISR_INT_MASK) { + aspeed_jtag_write(aspeed_jtag, + (status & ASPEED_JTAG_ISR_INT_MASK) + | (status & ASPEED_JTAG_ISR_INT_EN_MASK), + ASPEED_JTAG_ISR); + aspeed_jtag->flag |= status & ASPEED_JTAG_ISR_INT_MASK; + } + + if (aspeed_jtag->flag) { + wake_up_interruptible(&aspeed_jtag->jtag_wq); + ret = IRQ_HANDLED; + } else { + dev_err(aspeed_jtag->dev, "irq status:%x\n", + status); + ret = IRQ_NONE; + } + return ret; +} +#endif + +static int aspeed_jtag_enable(struct jtag *jtag) +{ + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + + aspeed_jtag_master(aspeed_jtag); + return 0; +} + +static int aspeed_jtag_disable(struct jtag *jtag) +{ + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + + aspeed_jtag_slave(aspeed_jtag); + return 0; +} + +static int aspeed_jtag_init(struct platform_device *pdev, + struct aspeed_jtag *aspeed_jtag) +{ + struct resource *res; + struct resource *scu_res; + struct resource *scupin_res; +#ifdef USE_INTERRUPTS + int err; +#endif + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + aspeed_jtag->reg_base = devm_ioremap_resource(aspeed_jtag->dev, res); + if (IS_ERR(aspeed_jtag->reg_base)) + return -ENOMEM; + + scu_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + aspeed_jtag->scu_base = devm_ioremap_resource(aspeed_jtag->dev, + scu_res); + if (IS_ERR(aspeed_jtag->scu_base)) + return -ENOMEM; + + if (of_device_is_compatible(pdev->dev.of_node, "aspeed,ast2600-jtag")) { + scupin_res = platform_get_resource(pdev, IORESOURCE_MEM, 2); + aspeed_jtag->scupin_ctrl = devm_ioremap_resource(aspeed_jtag->dev, + scupin_res); + if (IS_ERR(aspeed_jtag->scupin_ctrl)) + return -ENOMEM; + + aspeed_jtag->scu_clear_reg = 1; + } else { + aspeed_jtag->scupin_ctrl = NULL; + aspeed_jtag->scu_clear_reg = 0; + } + + aspeed_jtag->pclk = devm_clk_get(aspeed_jtag->dev, NULL); + if (IS_ERR(aspeed_jtag->pclk)) { + dev_err(aspeed_jtag->dev, "devm_clk_get failed\n"); + return PTR_ERR(aspeed_jtag->pclk); + } + +#ifdef USE_INTERRUPTS + aspeed_jtag->irq = platform_get_irq(pdev, 0); + if (aspeed_jtag->irq < 0) { + dev_err(aspeed_jtag->dev, "no irq specified\n"); + return -ENOENT; + } +#endif + + if (clk_prepare_enable(aspeed_jtag->pclk)) { + dev_err(aspeed_jtag->dev, "no irq specified\n"); + return -ENOENT; + } + + aspeed_jtag->rst = devm_reset_control_get_shared(&pdev->dev, NULL); + if (IS_ERR(aspeed_jtag->rst)) { + dev_err(aspeed_jtag->dev, + "missing or invalid reset controller device tree entry"); + return PTR_ERR(aspeed_jtag->rst); + } + reset_control_deassert(aspeed_jtag->rst); + +#ifdef USE_INTERRUPTS + err = devm_request_irq(aspeed_jtag->dev, aspeed_jtag->irq, + aspeed_jtag_interrupt, 0, + "aspeed-jtag", aspeed_jtag); + if (err) { + dev_err(aspeed_jtag->dev, "unable to get IRQ"); + clk_disable_unprepare(aspeed_jtag->pclk); + return err; + } +#endif + + aspeed_jtag_slave(aspeed_jtag); + + aspeed_jtag->flag = 0; + aspeed_jtag->mode = 0; + init_waitqueue_head(&aspeed_jtag->jtag_wq); + return 0; +} + +#ifdef CONFIG_JTAG_ASPEED_LEGACY_UIO +static u32 g_sw_tdi; +static u32 g_sw_tck; +static u32 g_sw_tms; + +#define JTAG_SW_MODE_VAL_MASK (ASPEED_JTAG_SW_MODE_TDIO | \ + ASPEED_JTAG_SW_MODE_TCK | ASPEED_JTAG_SW_MODE_TMS) + +static ssize_t show_tdo(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct jtag *jtag = dev_get_drvdata(dev); + struct aspeed_jtag *ast_jtag = jtag_priv(jtag); + u32 val = aspeed_jtag_read(ast_jtag, ASPEED_JTAG_SW); + + return sprintf(buf, "%s\n", (val & ASPEED_JTAG_SW_MODE_TDIO) ? + "1" : "0"); +} + +static DEVICE_ATTR(tdo, S_IRUGO, show_tdo, NULL); + +static void aspeed_jtag_write_sw_reg(struct device *dev) +{ + struct jtag *jtag = dev_get_drvdata(dev); + struct aspeed_jtag *ast_jtag = jtag_priv(jtag); + u32 old_val = aspeed_jtag_read(ast_jtag, ASPEED_JTAG_SW); + u32 new_val = (old_val & ~JTAG_SW_MODE_VAL_MASK) | + (g_sw_tdi | g_sw_tck | g_sw_tms); + + aspeed_jtag_write(ast_jtag, new_val, ASPEED_JTAG_SW); +} + +#define STORE_COMMON(dev, buf, count, tdx, true_value) do { \ + unsigned long val; \ + int err; \ + err = kstrtoul(buf, 0, &val); \ + if (err) \ + return err; \ + tdx = val ? true_value : 0; \ + aspeed_jtag_write_sw_reg(dev); \ + return count; \ +} while (0); + +static ssize_t store_tdi(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + STORE_COMMON(dev, buf, count, g_sw_tdi, ASPEED_JTAG_SW_MODE_TDIO); +} + +static DEVICE_ATTR(tdi, S_IWUSR, NULL, store_tdi); + +static ssize_t store_tms(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + STORE_COMMON(dev, buf, count, g_sw_tms, ASPEED_JTAG_SW_MODE_TMS); +} + +static DEVICE_ATTR(tms, S_IWUSR, NULL, store_tms); + +static ssize_t store_tck(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + STORE_COMMON(dev, buf, count, g_sw_tck, ASPEED_JTAG_SW_MODE_TCK); +} + +static DEVICE_ATTR(tck, S_IWUSR, NULL, store_tck); + +static ssize_t show_sts(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct jtag *jtag = dev_get_drvdata(dev); + struct aspeed_jtag *ast_jtag = jtag_priv(jtag); + + /* + * NOTE: not all the defined states are supported, and this is + * to make sure kernel ABI is consistent with old kernel. + */ + switch (ast_jtag->status) { + case JTAG_STATE_IDLE: + case JTAG_STATE_PAUSEIR: + case JTAG_STATE_PAUSEDR: + return sprintf(buf, "%s\n", end_status_str[ast_jtag->status]); + + default: + break; + } + + return sprintf(buf, "ERROR\n"); +} + +static DEVICE_ATTR(sts, S_IRUGO, show_sts, NULL); + +static ssize_t show_frequency(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u32 frq; + struct jtag *jtag = dev_get_drvdata(dev); + + aspeed_jtag_freq_get(jtag, &frq); + + return sprintf(buf, "Frequency : %d\n", frq); +} + +static ssize_t store_frequency(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long val; + struct jtag *jtag = dev_get_drvdata(dev); + int err; + err = kstrtoul(buf, 0, &val); + if (err) + return err; + aspeed_jtag_freq_set(jtag, val); + + return count; +} + +static DEVICE_ATTR(freq, S_IRUGO | S_IWUSR, show_frequency, store_frequency); + +static struct attribute *ast_jtag_sysfs_entries[] = { + &dev_attr_freq.attr, + &dev_attr_sts.attr, + &dev_attr_tck.attr, + &dev_attr_tms.attr, + &dev_attr_tdi.attr, + &dev_attr_tdo.attr, + NULL +}; + +static struct attribute_group ast_jtag_attr_group = { + .attrs = ast_jtag_sysfs_entries, +}; + +struct run_cycle_param { + unsigned char tdi; + unsigned char tms; + unsigned char tck; + unsigned char tdo; +}; +#define JTAG_RUN_CYCLE _IOR(__JTAG_IOCTL_MAGIC, 11, struct run_cycle_param) + +static void aspeed_jtag_run_cycle(struct jtag *jtag, + struct run_cycle_param *run_cycle) +{ + u32 new_val; + struct aspeed_jtag *ast_jtag = jtag_priv(jtag); + u32 old_val = aspeed_jtag_read(ast_jtag, ASPEED_JTAG_SW); + + g_sw_tdi = run_cycle->tdi ? ASPEED_JTAG_SW_MODE_TDIO : 0; + g_sw_tms = run_cycle->tms ? ASPEED_JTAG_SW_MODE_TMS : 0; + g_sw_tck = run_cycle->tck ? ASPEED_JTAG_SW_MODE_TCK : 0; + new_val = (old_val & ~JTAG_SW_MODE_VAL_MASK) | + (g_sw_tdi | g_sw_tck | g_sw_tms); + aspeed_jtag_write(ast_jtag, new_val, ASPEED_JTAG_SW); + + new_val = aspeed_jtag_read(ast_jtag, ASPEED_JTAG_SW); + run_cycle->tdo = (new_val & ASPEED_JTAG_SW_MODE_TDIO) ? 1 : 0; +} + +static int aspeed_jtag_ioctl(struct jtag *jtag, unsigned int cmd, + unsigned long arg) +{ + int err = 0; + struct run_cycle_param jtag_run_cycle; + + if (!arg) + return -EINVAL; + + switch (cmd) { + case JTAG_RUN_CYCLE: + if (copy_from_user(&jtag_run_cycle, (void __user*)arg, + sizeof(struct run_cycle_param))) { + err = -EFAULT; + break; + } + + aspeed_jtag_run_cycle(jtag, &jtag_run_cycle); + + if (copy_to_user((void __user*)(arg), &jtag_run_cycle, + sizeof(struct run_cycle_param))) + err = -EFAULT; + + break; + + default: + return -EINVAL; + } + + return err; +} + +#endif /* CONFIG_JTAG_ASPEED_LEGACY_UIO */ + +static int aspeed_jtag_deinit(struct platform_device *pdev, + struct aspeed_jtag *aspeed_jtag) +{ +#ifdef CONFIG_JTAG_ASPEED_LEGACY_UIO + sysfs_remove_group(&pdev->dev.kobj, &ast_jtag_attr_group); +#endif + aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_ISR); + /* Disable clock */ + aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_CTRL); + reset_control_assert(aspeed_jtag->rst); + clk_disable_unprepare(aspeed_jtag->pclk); + return 0; +} + +static const struct jtag_ops aspeed_jtag_ops = { + .freq_get = aspeed_jtag_freq_get, + .freq_set = aspeed_jtag_freq_set, + .status_get = aspeed_jtag_status_get, + .status_set = aspeed_jtag_status_set, + .xfer = aspeed_jtag_xfer, + .mode_set = aspeed_jtag_mode_set, + .bitbang = aspeed_jtag_bitbang, + .enable = aspeed_jtag_enable, + .disable = aspeed_jtag_disable, +#ifdef CONFIG_JTAG_ASPEED_LEGACY_UIO + .ioctl = aspeed_jtag_ioctl, +#endif +}; + +static int aspeed_jtag_probe(struct platform_device *pdev) +{ + struct aspeed_jtag *aspeed_jtag; + struct jtag *jtag; + int err; + + jtag = jtag_alloc(&pdev->dev, sizeof(*aspeed_jtag), &aspeed_jtag_ops); + if (!jtag) + return -ENOMEM; + + platform_set_drvdata(pdev, jtag); + aspeed_jtag = jtag_priv(jtag); + aspeed_jtag->dev = &pdev->dev; + + /* Initialize device*/ + err = aspeed_jtag_init(pdev, aspeed_jtag); + if (err) + goto err_jtag_init; + + /* Initialize JTAG core structure*/ + err = devm_jtag_register(aspeed_jtag->dev, jtag); + if (err) + goto err_jtag_register; + +#ifdef CONFIG_JTAG_ASPEED_LEGACY_UIO + err = sysfs_create_group(&pdev->dev.kobj, &ast_jtag_attr_group); + if (err) + goto err_jtag_register; +#endif /* CONFIG_JTAG_ASPEED_LEGACY_UIO */ + + return 0; + +err_jtag_register: + aspeed_jtag_deinit(pdev, aspeed_jtag); +err_jtag_init: + jtag_free(jtag); + return err; +} + +static int aspeed_jtag_remove(struct platform_device *pdev) +{ + struct jtag *jtag = platform_get_drvdata(pdev); + + aspeed_jtag_deinit(pdev, jtag_priv(jtag)); + return 0; +} + +static const struct of_device_id aspeed_jtag_of_match[] = { + { .compatible = "aspeed,ast2400-jtag", }, + { .compatible = "aspeed,ast2500-jtag", }, + { .compatible = "aspeed,ast2600-jtag", }, + {} +}; + +static struct platform_driver aspeed_jtag_driver = { + .probe = aspeed_jtag_probe, + .remove = aspeed_jtag_remove, + .driver = { + .name = ASPEED_JTAG_NAME, + .of_match_table = aspeed_jtag_of_match, + }, +}; +module_platform_driver(aspeed_jtag_driver); + +MODULE_AUTHOR("Oleksandr Shamray "); +MODULE_DESCRIPTION("ASPEED JTAG driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/jtag/jtag.c b/drivers/jtag/jtag.c new file mode 100644 index 00000000000000..090f56d582a4cd --- /dev/null +++ b/drivers/jtag/jtag.c @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: GPL-2.0-only +// drivers/jtag/jtag.c +// +// Copyright (c) 2018 Mellanox Technologies. All rights reserved. +// Copyright (c) 2018 Oleksandr Shamray + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct jtag { + struct miscdevice miscdev; + const struct jtag_ops *ops; + int id; + unsigned long priv[0]; +}; + +static DEFINE_IDA(jtag_ida); + +void *jtag_priv(struct jtag *jtag) +{ + return jtag->priv; +} +EXPORT_SYMBOL_GPL(jtag_priv); + +static long jtag_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct jtag *jtag = file->private_data; + struct jtag_end_tap_state endstate; + struct jtag_xfer xfer; + struct tck_bitbang bitbang; + struct jtag_mode mode; + u8 *xfer_data; + u32 data_size; + u32 value; + int err; + + if (!arg) + return -EINVAL; + + switch (cmd) { + case JTAG_GIOCFREQ: + if (!jtag->ops->freq_get) + return -EOPNOTSUPP; + + err = jtag->ops->freq_get(jtag, &value); + if (err) + break; + + if (put_user(value, (__u32 __user *)arg)) + err = -EFAULT; + break; + + case JTAG_SIOCFREQ: + if (!jtag->ops->freq_set) + return -EOPNOTSUPP; + + if (get_user(value, (__u32 __user *)arg)) + return -EFAULT; + if (value == 0) + return -EINVAL; + + err = jtag->ops->freq_set(jtag, value); + break; + + case JTAG_SIOCSTATE: + if (copy_from_user(&endstate, (const void __user *)arg, + sizeof(struct jtag_end_tap_state))) + return -EFAULT; + + if (endstate.endstate > JTAG_STATE_UPDATEIR) + return -EINVAL; + + if (endstate.reset > JTAG_FORCE_RESET) + return -EINVAL; + + err = jtag->ops->status_set(jtag, &endstate); + 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.endstate > JTAG_STATE_UPDATEIR) + return -EINVAL; + + data_size = DIV_ROUND_UP(xfer.length, BITS_PER_BYTE); + xfer_data = memdup_user(u64_to_user_ptr(xfer.tdio), data_size); + if (IS_ERR(xfer_data)) + return -EFAULT; + + err = jtag->ops->xfer(jtag, &xfer, xfer_data); + if (err) { + kfree(xfer_data); + return err; + } + + err = copy_to_user(u64_to_user_ptr(xfer.tdio), + (void *)xfer_data, data_size); + kfree(xfer_data); + if (err) + return -EFAULT; + + if (copy_to_user((void __user *)arg, (void *)&xfer, + sizeof(struct jtag_xfer))) + return -EFAULT; + break; + + case JTAG_GIOCSTATUS: + err = jtag->ops->status_get(jtag, &value); + if (err) + break; + + err = put_user(value, (__u32 __user *)arg); + break; + case JTAG_IOCBITBANG: + if (copy_from_user(&bitbang, (const void __user *)arg, + sizeof(struct tck_bitbang))) + return -EFAULT; + err = jtag->ops->bitbang(jtag, &bitbang); + if (err) + break; + + if (copy_to_user((void __user *)arg, (void *)&bitbang, + sizeof(struct tck_bitbang))) + return -EFAULT; + break; + case JTAG_SIOCMODE: + if (!jtag->ops->mode_set) + return -EOPNOTSUPP; + + if (copy_from_user(&mode, (const void __user *)arg, + sizeof(struct jtag_mode))) + return -EFAULT; + + err = jtag->ops->mode_set(jtag, &mode); + break; + + default: + if (jtag->ops->ioctl) + return jtag->ops->ioctl(jtag, cmd, arg); + return -EINVAL; + } + return err; +} + +static int jtag_open(struct inode *inode, struct file *file) +{ + struct jtag *jtag = container_of(file->private_data, + struct jtag, + miscdev); + + file->private_data = jtag; + if (jtag->ops->enable(jtag)) + return -EBUSY; + return nonseekable_open(inode, file); +} + +static int jtag_release(struct inode *inode, struct file *file) +{ + struct jtag *jtag = file->private_data; + + if (jtag->ops->disable(jtag)) + return -EBUSY; + return 0; +} + +static const struct file_operations jtag_fops = { + .owner = THIS_MODULE, + .open = jtag_open, + .llseek = noop_llseek, + .unlocked_ioctl = jtag_ioctl, + .release = jtag_release, +}; + +struct jtag *jtag_alloc(struct device *host, size_t priv_size, + const struct jtag_ops *ops) +{ + struct jtag *jtag; + + if (!host) + return NULL; + + if (!ops) + return NULL; + + if (!ops->status_set || !ops->status_get || !ops->xfer) + return NULL; + + jtag = kzalloc(sizeof(*jtag) + priv_size, GFP_KERNEL); + if (!jtag) + return NULL; + + jtag->ops = ops; + jtag->miscdev.parent = host; + + return jtag; +} +EXPORT_SYMBOL_GPL(jtag_alloc); + +void jtag_free(struct jtag *jtag) +{ + kfree(jtag); +} +EXPORT_SYMBOL_GPL(jtag_free); + +static int jtag_register(struct jtag *jtag) +{ + struct device *dev = jtag->miscdev.parent; + 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.fops = &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_jtag_alloc; + } + + err = misc_register(&jtag->miscdev); + if (err) { + dev_err(jtag->miscdev.parent, "Unable to register device\n"); + goto err_jtag_name; + } + return 0; + +err_jtag_name: + kfree(jtag->miscdev.name); +err_jtag_alloc: + ida_simple_remove(&jtag_ida, id); + return err; +} + +static void jtag_unregister(struct jtag *jtag) +{ + misc_deregister(&jtag->miscdev); + kfree(jtag->miscdev.name); + ida_simple_remove(&jtag_ida, jtag->id); +} + +static void devm_jtag_unregister(struct device *dev, void *res) +{ + jtag_unregister(*(struct jtag **)res); +} + +int devm_jtag_register(struct device *dev, struct jtag *jtag) +{ + struct jtag **ptr; + int ret; + + ptr = devres_alloc(devm_jtag_unregister, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = jtag_register(jtag); + if (!ret) { + *ptr = jtag; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + return ret; +} +EXPORT_SYMBOL_GPL(devm_jtag_register); + +static void __exit jtag_exit(void) +{ + ida_destroy(&jtag_ida); +} + +module_exit(jtag_exit); + +MODULE_AUTHOR("Oleksandr Shamray "); +MODULE_DESCRIPTION("Generic jtag support"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/jtag.h b/include/linux/jtag.h new file mode 100644 index 00000000000000..6a6b53a549d879 --- /dev/null +++ b/include/linux/jtag.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +// include/linux/jtag.h - JTAG class driver +// +// Copyright (c) 2018 Mellanox Technologies. All rights reserved. +// Copyright (c) 2018 Oleksandr Shamray + +#ifndef __JTAG_H +#define __JTAG_H + +#include +#include + +#define JTAG_MAX_XFER_DATA_LEN 65535 + +struct jtag; +/** + * struct jtag_ops - callbacks for JTAG control functions: + * + * @freq_get: get frequency function. Filled by dev driver + * @freq_set: set frequency function. Filled by dev driver + * @status_get: get JTAG TAPC state function. Mandatory, Filled by dev driver + * @status_set: set JTAG TAPC state function. Mandatory, Filled by dev driver + * @xfer: send JTAG xfer function. Mandatory func. Filled by dev driver + * @mode_set: set specific work mode for JTAG. Filled by dev driver + * @ioctl: handle driver specific ioctl requests. Filled by dev driver + */ +struct jtag_ops { + int (*freq_get)(struct jtag *jtag, u32 *freq); + int (*freq_set)(struct jtag *jtag, u32 freq); + int (*status_get)(struct jtag *jtag, u32 *state); + int (*status_set)(struct jtag *jtag, struct jtag_end_tap_state *endst); + int (*xfer)(struct jtag *jtag, struct jtag_xfer *xfer, u8 *xfer_data); + int (*mode_set)(struct jtag *jtag, struct jtag_mode *jtag_mode); + int (*bitbang)(struct jtag *jtag, struct tck_bitbang *tck_bitbang); + int (*enable)(struct jtag *jtag); + int (*disable)(struct jtag *jtag); + int (*ioctl)(struct jtag *jtag, unsigned int cmd, unsigned long arg); +}; + +void *jtag_priv(struct jtag *jtag); +int devm_jtag_register(struct device *dev, struct jtag *jtag); +struct jtag *jtag_alloc(struct device *host, size_t priv_size, + const struct jtag_ops *ops); +void jtag_free(struct jtag *jtag); + +#endif /* __JTAG_H */ diff --git a/include/uapi/linux/jtag.h b/include/uapi/linux/jtag.h new file mode 100644 index 00000000000000..3f9e1953f5a436 --- /dev/null +++ b/include/uapi/linux/jtag.h @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +// include/uapi/linux/jtag.h - JTAG class driver uapi +// +// Copyright (c) 2018 Mellanox Technologies. All rights reserved. +// Copyright (c) 2018 Oleksandr Shamray + +#ifndef __UAPI_LINUX_JTAG_H +#define __UAPI_LINUX_JTAG_H + +/* + * JTAG_XFER_MODE: JTAG transfer mode. Used to set JTAG controller transfer mode + * This is bitmask for feature param in jtag_mode for ioctl JTAG_SIOCMODE + */ +#define JTAG_XFER_MODE 0 +/* + * JTAG_CONTROL_MODE: JTAG controller mode. Used to set JTAG controller mode + * This is bitmask for feature param in jtag_mode for ioctl JTAG_SIOCMODE + */ +#define JTAG_CONTROL_MODE 1 +/* + * JTAG_SLAVE_MODE: JTAG slave mode. Used to set JTAG controller slave mode + * This is bitmask for mode param in jtag_mode for ioctl JTAG_SIOCMODE + */ +#define JTAG_SLAVE_MODE 0 +/* + * JTAG_MASTER_MODE: JTAG master mode. Used to set JTAG controller master mode + * This is bitmask for mode param in jtag_mode for ioctl JTAG_SIOCMODE + */ +#define JTAG_MASTER_MODE 1 +/* + * JTAG_XFER_HW_MODE: JTAG hardware mode. Used to set HW drived or bitbang + * mode. This is bitmask for mode param in jtag_mode for ioctl JTAG_SIOCMODE + */ +#define JTAG_XFER_HW_MODE 1 +/* + * JTAG_XFER_SW_MODE: JTAG software mode. Used to set SW drived or bitbang + * mode. This is bitmask for mode param in jtag_mode for ioctl JTAG_SIOCMODE + */ +#define JTAG_XFER_SW_MODE 0 + +/** + * enum jtag_endstate: + * + * @JTAG_STATE_TLRESET: JTAG state machine Test Logic Reset state + * @JTAG_STATE_IDLE: JTAG state machine IDLE state + * @JTAG_STATE_SELECTDR: JTAG state machine SELECT_DR state + * @JTAG_STATE_CAPTUREDR: JTAG state machine CAPTURE_DR state + * @JTAG_STATE_SHIFTDR: JTAG state machine SHIFT_DR state + * @JTAG_STATE_EXIT1DR: JTAG state machine EXIT-1 DR state + * @JTAG_STATE_PAUSEDR: JTAG state machine PAUSE_DR state + * @JTAG_STATE_EXIT2DR: JTAG state machine EXIT-2 DR state + * @JTAG_STATE_UPDATEDR: JTAG state machine UPDATE DR state + * @JTAG_STATE_SELECTIR: JTAG state machine SELECT_IR state + * @JTAG_STATE_CAPTUREIR: JTAG state machine CAPTURE_IR state + * @JTAG_STATE_SHIFTIR: JTAG state machine SHIFT_IR state + * @JTAG_STATE_EXIT1IR: JTAG state machine EXIT-1 IR state + * @JTAG_STATE_PAUSEIR: JTAG state machine PAUSE_IR state + * @JTAG_STATE_EXIT2IR: JTAG state machine EXIT-2 IR state + * @JTAG_STATE_UPDATEIR: JTAG state machine UPDATE IR state + */ +enum jtag_endstate { + JTAG_STATE_TLRESET, + JTAG_STATE_IDLE, + JTAG_STATE_SELECTDR, + JTAG_STATE_CAPTUREDR, + JTAG_STATE_SHIFTDR, + JTAG_STATE_EXIT1DR, + JTAG_STATE_PAUSEDR, + JTAG_STATE_EXIT2DR, + JTAG_STATE_UPDATEDR, + JTAG_STATE_SELECTIR, + JTAG_STATE_CAPTUREIR, + JTAG_STATE_SHIFTIR, + JTAG_STATE_EXIT1IR, + JTAG_STATE_PAUSEIR, + JTAG_STATE_EXIT2IR, + JTAG_STATE_UPDATEIR +}; + +/** + * enum jtag_reset: + * + * @JTAG_NO_RESET: JTAG run TAP from current state + * @JTAG_FORCE_RESET: JTAG force TAP to reset state + */ +enum jtag_reset { + JTAG_NO_RESET = 0, + JTAG_FORCE_RESET = 1, +}; + +/** + * enum jtag_xfer_type: + * + * @JTAG_SIR_XFER: SIR transfer + * @JTAG_SDR_XFER: SDR transfer + */ +enum jtag_xfer_type { + JTAG_SIR_XFER = 0, + JTAG_SDR_XFER = 1, +}; + +/** + * enum jtag_xfer_direction: + * + * @JTAG_READ_XFER: read transfer + * @JTAG_WRITE_XFER: write transfer + * @JTAG_READ_WRITE_XFER: read & write transfer + */ +enum jtag_xfer_direction { + JTAG_READ_XFER = 1, + JTAG_WRITE_XFER = 2, + JTAG_READ_WRITE_XFER = 3, +}; + +/** + * struct jtag_end_tap_state - forces JTAG state machine to go into a TAPC + * state + * + * @reset: 0 - run IDLE/PAUSE from current state + * 1 - go through TEST_LOGIC/RESET state before IDLE/PAUSE + * @end: completion flag + * @tck: clock counter + * + * Structure provide interface to JTAG device for JTAG set state execution. + */ +struct jtag_end_tap_state { + __u8 reset; + __u8 endstate; + __u8 tck; +}; + +/** + * struct jtag_xfer - jtag xfer: + * + * @type: transfer type + * @direction: xfer direction + * @length: xfer bits len + * @tdio : xfer data array + * @endir: xfer end state + * + * Structure provide interface to JTAG device for JTAG SDR/SIR xfer execution. + */ +struct jtag_xfer { + __u8 type; + __u8 direction; + __u8 endstate; + __u8 padding; + __u32 length; + __u64 tdio; +}; + +/** + * struct jtag_bitbang - jtag bitbang: + * + * @tms: JTAG TMS + * @tdi: JTAG TDI (input) + * @tdo: JTAG TDO (output) + * + * Structure provide interface to JTAG device for JTAG bitbang execution. + */ +struct tck_bitbang { + __u8 tms; + __u8 tdi; + __u8 tdo; +} __attribute__((__packed__)); + +/** + * struct jtag_mode - jtag mode: + * + * @feature: 0 - JTAG feature setting selector for JTAG controller HW/SW + * 1 - JTAG feature setting selector for controller + * bus(master/slave) mode. + * @mode: (0 - SW / 1 - HW) for JTAG_XFER_MODE feature(0) + * (0 - Slave / 1 - Master) for JTAG_CONTROL_MODE feature(1) + * + * Structure provide configuration modes to JTAG device. + */ +struct jtag_mode { + __u32 feature; + __u32 mode; +}; + +/* ioctl interface */ +#define __JTAG_IOCTL_MAGIC 0xb2 + +#define JTAG_SIOCSTATE _IOW(__JTAG_IOCTL_MAGIC, 0, struct jtag_end_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 jtag_endstate) +#define JTAG_SIOCMODE _IOW(__JTAG_IOCTL_MAGIC, 5, unsigned int) +#define JTAG_IOCBITBANG _IOW(__JTAG_IOCTL_MAGIC, 6, unsigned int) + +#endif /* __UAPI_LINUX_JTAG_H */