From patchwork Mon May 28 12:00:25 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Oleksandr Shamray X-Patchwork-Id: 10432237 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 55069602CB for ; Mon, 28 May 2018 12:28:16 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 42FFF281D2 for ; Mon, 28 May 2018 12:28:16 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 367A3284C4; Mon, 28 May 2018 12:28:16 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, MAILING_LIST_MULTI, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id D4DCC281D2 for ; Mon, 28 May 2018 12:28:14 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:Cc:List-Subscribe: List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id:References: In-Reply-To:Message-Id:Date:Subject:To:From:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Owner; bh=9Wf0l3SXysO0yCXQVifTprhb7v1aLx+qtiyGu7t3370=; b=gjBZADZ481ClA+Y8JWSq94ghUE LUf216u6K0ZDMwUogzZI+68H1ICbBIQNnhiUdd47REhA0bpZUL9cqhN/p8ENGbhYaPZiJGOo3mSFq zLcObb9emtb6OTWyUgZwy7mLgkX8g8qpOSomlx9Vp7cCeV6p0/q85t+weWG9aTftG0jIBbH0t+ahm mnqfpF0pK3D1VZtsI2s8YV3crf9lgGmuuDD4UQuplwpODHWVwcCYnTOecEXs0zumkTou0imsoFT7w cwEcTA54UI+Wu1Z38r/TDDAjVxUsUnmymffDQED3p4LJlsxFrDrZbD49Uiu4KX+KfOEHy7+7dvJBY A2hp9q6w==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.90_1 #2 (Red Hat Linux)) id 1fNHFr-0000Ln-Ib; Mon, 28 May 2018 12:28:03 +0000 Received: from mail-il-dmz.mellanox.com ([193.47.165.129] helo=mellanox.co.il) by bombadil.infradead.org with esmtp (Exim 4.90_1 #2 (Red Hat Linux)) id 1fNHEW-000860-Jx for linux-arm-kernel@lists.infradead.org; Mon, 28 May 2018 12:26:47 +0000 Received: from Internal Mail-Server by MTLPINE1 (envelope-from oleksandrs@mellanox.com) with ESMTPS (AES256-SHA encrypted); 28 May 2018 15:02:35 +0300 Received: from r-vnc16.mtr.labs.mlnx (r-vnc16.mtr.labs.mlnx [10.208.0.16]) by labmailer.mlnx (8.13.8/8.13.8) with ESMTP id w4SC0SdT023011; Mon, 28 May 2018 15:00:28 +0300 From: Oleksandr Shamray To: gregkh@linuxfoundation.org, arnd@arndb.de Subject: [patch v22 2/4] drivers: jtag: Add Aspeed SoC 24xx and 25xx families JTAG master driver Date: Mon, 28 May 2018 15:00:25 +0300 Message-Id: <1527508827-30724-3-git-send-email-oleksandrs@mellanox.com> X-Mailer: git-send-email 1.7.1 In-Reply-To: <1527508827-30724-1-git-send-email-oleksandrs@mellanox.com> References: <1527508827-30724-1-git-send-email-oleksandrs@mellanox.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20180528_052641_320154_764036D6 X-CRM114-Status: GOOD ( 20.51 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: devicetree@vger.kernel.org, jiri@resnulli.us, system-sw-low-level@mellanox.com, linux-api@vger.kernel.org, openbmc@lists.ozlabs.org, linux-kernel@vger.kernel.org, openocd-devel-owner@lists.sourceforge.net, robh+dt@kernel.org, joel@jms.id.au, linux-serial@vger.kernel.org, Oleksandr Shamray , tklauser@distanz.ch, mchehab@kernel.org, davem@davemloft.net, linux-arm-kernel@lists.infradead.org MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP Driver adds support of Aspeed 2500/2400 series SOC JTAG master controller. Driver implements the following jtag ops: - freq_get; - freq_set; - status_get; - idle; - xfer; It has been tested on Mellanox system with BMC equipped with Aspeed 2520 SoC for programming CPLD devices. Signed-off-by: Oleksandr Shamray Signed-off-by: Jiri Pirko Acked-by: Arnd Bergmann Acked-by: Philippe Ombredanne Acked-by: Joel Stanley --- v21->v22 Comments pointed by Andy Shevchenko - rearrange ASPEED register defines - simplified JTAG divider calculation formula - change delay function in bit-bang operation - add helper functions for TAP states switching - remove unnecessary comments - remove redundant debug messages - make dines for repetative register bit sets - fixed indentation - change checks from negative to positive - add error check for clk_prepare_enable - rework driver alloc/register to devm_ variant - Increasing line length up to 85 in order to improve readability v20->v21 v19->v20 Notifications from kbuild test robot - add static declaration to 'aspeed_jtag_init' and 'aspeed_jtag_deinit' functions v18->v19 v17->v18 v16->v17 v15->v16 Comments pointed by Joel Stanley - Add reset_control on Jtag init/deinit v14->v15 Comments pointed by Joel Stanley - Add ARCH_ASPEED || COMPILE_TEST to Kconfig - remove unused offset variable - remove "aspeed_jtag" from dev_err and dev_dbg messages - change clk_prepare_enable initialisation order v13->v14 Comments pointed by Philippe Ombredanne - Change style of head block comment from /**/ to // v12->v13 Comments pointed by Philippe Ombredanne - Change jtag-aspeed.c licence type to SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note and reorder line with license- add reset descriptions in bndings file in description Comments pointed by Kun Yi - Changed capability check for aspeed,ast2400-jtag/ast200-jtag v11->v12 Comments pointed by Chip Bilbrey - Remove access mode from xfer and idle transactions - Add new ioctl JTAG_SIOCMODE for set hw mode v10->v11 v9->v10 V8->v9 Comments pointed by Arnd Bergmann - add *data parameter to xfer function prototype v7->v8 Comments pointed by Joel Stanley - aspeed_jtag_init replace goto to return; - change input variables type from __u32 to u32 in functios freq_get, freq_set, status_get - change sm_ variables type from char to u8 - in jatg_init add disable clocks on error case - remove release_mem_region on error case - remove devm_free_irq on jtag_deinit - Fix misspelling Disabe/Disable - Change compatible string to ast2400 and ast2000 v6->v7 Notifications from kbuild test robot - Add include to jtag-asapeed.c v5->v6 v4->v5 Comments pointed by Arnd Bergmann - Added HAS_IOMEM dependence in Kconfig to avoid "undefined reference to `devm_ioremap_resource'" error, because in some arch this not supported v3->v4 Comments pointed by Arnd Bergmann - change transaction pointer tdio type to __u64 - change internal status type from enum to __u32 v2->v3 v1->v2 Comments pointed by Greg KH - change license type from GPLv2/BSD to GPLv2 Comments pointed by Neil Armstrong - Add clk_prepare_enable/clk_disable_unprepare in clock init/deinit - Change .compatible to soc-specific compatible names aspeed,aspeed4000-jtag/aspeed5000-jtag - Added dt-bindings Comments pointed by Arnd Bergmann - Reorder functions and removed the forward declarations - Add static const qualifier to state machine states transitions - Change .compatible to soc-specific compatible names aspeed,aspeed4000-jtag/aspeed5000-jtag - Add dt-bindings Comments pointed by Randy Dunlap - Change module name jtag-aspeed in description in Kconfig Comments pointed by kbuild test robot - Remove invalid include - add resource_size instead of calculation --- drivers/jtag/Kconfig | 14 + drivers/jtag/Makefile | 1 + drivers/jtag/jtag-aspeed.c | 769 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 784 insertions(+), 0 deletions(-) create mode 100644 drivers/jtag/jtag-aspeed.c diff --git a/drivers/jtag/Kconfig b/drivers/jtag/Kconfig index 47771fc..0cc163f 100644 --- a/drivers/jtag/Kconfig +++ b/drivers/jtag/Kconfig @@ -15,3 +15,17 @@ menuconfig JTAG 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. diff --git a/drivers/jtag/Makefile b/drivers/jtag/Makefile index af37493..04a855e 100644 --- a/drivers/jtag/Makefile +++ b/drivers/jtag/Makefile @@ -1 +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 0000000..b3559f8 --- /dev/null +++ b/drivers/jtag/jtag-aspeed.c @@ -0,0 +1,769 @@ +// 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 + +#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_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_READ_XFER) ? UINT_MAX : byte) + +#define ASPEED_JTAG_TCK_WAIT 10 +#define ASPEED_JTAG_RESET_CNTR 10 + +#define ASPEED_JTAG_NAME "jtag-aspeed" + +struct aspeed_jtag { + void __iomem *reg_base; + 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; +}; + +static char *end_status_str[] = {"idle", "ir pause", "drpause"}; + +static u32 aspeed_jtag_read(struct aspeed_jtag *aspeed_jtag, u32 reg) +{ + return readl(aspeed_jtag->reg_base + reg); +} + +static void +aspeed_jtag_write(struct aspeed_jtag *aspeed_jtag, u32 val, u32 reg) +{ + 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 int aspeed_jtag_mode_set(struct jtag *jtag, u32 mode) +{ + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + + aspeed_jtag->mode = mode; + return 0; +} + +static void aspeed_jtag_sw_delay(struct aspeed_jtag *aspeed_jtag, int cnt) +{ + int i; + + for (i = 0; i < cnt; i++) + aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_SW); +} + +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); + + ndelay(ASPEED_JTAG_TCK_WAIT); + aspeed_jtag_sw_delay(aspeed_jtag, ASPEED_JTAG_TCK_WAIT); + + /* 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; + + aspeed_jtag_sw_delay(aspeed_jtag, ASPEED_JTAG_TCK_WAIT); + + /* 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); + return tdo; +} + +static void aspeed_jtag_wait_instruction_pause(struct aspeed_jtag *aspeed_jtag) +{ + wait_event_interruptible(aspeed_jtag->jtag_wq, + aspeed_jtag->flag & ASPEED_JTAG_ISR_INST_PAUSE); + aspeed_jtag->flag &= ~ASPEED_JTAG_ISR_INST_PAUSE; +} + +static void +aspeed_jtag_wait_instruction_complete(struct aspeed_jtag *aspeed_jtag) +{ + wait_event_interruptible(aspeed_jtag->jtag_wq, + aspeed_jtag->flag & ASPEED_JTAG_ISR_INST_COMPLETE); + aspeed_jtag->flag &= ~ASPEED_JTAG_ISR_INST_COMPLETE; +} + +static void +aspeed_jtag_wait_data_pause_complete(struct aspeed_jtag *aspeed_jtag) +{ + wait_event_interruptible(aspeed_jtag->jtag_wq, + aspeed_jtag->flag & ASPEED_JTAG_ISR_DATA_PAUSE); + aspeed_jtag->flag &= ~ASPEED_JTAG_ISR_DATA_PAUSE; +} + +static void aspeed_jtag_wait_data_complete(struct aspeed_jtag *aspeed_jtag) +{ + wait_event_interruptible(aspeed_jtag->jtag_wq, + aspeed_jtag->flag & ASPEED_JTAG_ISR_DATA_COMPLETE); + aspeed_jtag->flag &= ~ASPEED_JTAG_ISR_DATA_COMPLETE; +} + +static void aspeed_jtag_sm_cycle(struct aspeed_jtag *aspeed_jtag, + const u8 *tms, int len) +{ + int i; + + for (i = 0; i < len; i++) + aspeed_jtag_tck_cycle(aspeed_jtag, tms[i], 0); +} + +static void aspeed_jtag_run_idle(struct aspeed_jtag *aspeed_jtag, + struct jtag_run_test_idle *runtest) +{ + static const u8 sm_idle_irpause[] = {1, 1, 0, 1, 0}; + static const u8 sm_idle_drpause[] = {1, 0, 1, 0}; + + switch (runtest->endstate) { + case JTAG_STATE_PAUSEIR: + /* ->DRSCan->IRSCan->IRCap->IRExit1->PauseIR */ + aspeed_jtag_sm_cycle(aspeed_jtag, sm_idle_irpause, + sizeof(sm_idle_irpause)); + aspeed_jtag->status = JTAG_STATE_PAUSEIR; + break; + case JTAG_STATE_PAUSEDR: + /* ->DRSCan->DRCap->DRExit1->PauseDR */ + aspeed_jtag_sm_cycle(aspeed_jtag, sm_idle_drpause, + sizeof(sm_idle_drpause)); + + aspeed_jtag->status = JTAG_STATE_PAUSEDR; + break; + case JTAG_STATE_IDLE: + /* IDLE */ + aspeed_jtag_tck_cycle(aspeed_jtag, 0, 0); + aspeed_jtag->status = JTAG_STATE_IDLE; + break; + default: + break; + } +} + +static void aspeed_jtag_run_pause(struct aspeed_jtag *aspeed_jtag, + struct jtag_run_test_idle *runtest) +{ + static const u8 sm_pause_irpause[] = {1, 1, 1, 1, 0, 1, 0}; + static const u8 sm_pause_drpause[] = {1, 1, 1, 0, 1, 0}; + static const u8 sm_pause_idle[] = {1, 1, 0}; + + /* From IR/DR Pa.use */ + switch (runtest->endstate) { + case JTAG_STATE_PAUSEIR: + /* + * to Exit2 IR/DR->Updt IR/DR->DRSCan->IRSCan->IRCap-> + * IRExit1->PauseIR + */ + aspeed_jtag_sm_cycle(aspeed_jtag, sm_pause_irpause, + sizeof(sm_pause_irpause)); + + aspeed_jtag->status = JTAG_STATE_PAUSEIR; + break; + case JTAG_STATE_PAUSEDR: + /* + * to Exit2 IR/DR->Updt IR/DR->DRSCan->DRCap-> + * DRExit1->PauseDR + */ + aspeed_jtag_sm_cycle(aspeed_jtag, sm_pause_drpause, + sizeof(sm_pause_drpause)); + aspeed_jtag->status = JTAG_STATE_PAUSEDR; + break; + case JTAG_STATE_IDLE: + /* to Exit2 IR/DR->Updt IR/DR->IDLE */ + aspeed_jtag_sm_cycle(aspeed_jtag, sm_pause_idle, + sizeof(sm_pause_idle)); + aspeed_jtag->status = JTAG_STATE_IDLE; + break; + default: + break; + } +} + +static void aspeed_jtag_run_test_idle_sw(struct aspeed_jtag *aspeed_jtag, + struct jtag_run_test_idle *runtest) +{ + int i; + + /* SW mode from idle/pause-> to pause/idle */ + if (runtest->reset) { + for (i = 0; i < ASPEED_JTAG_RESET_CNTR; i++) + aspeed_jtag_tck_cycle(aspeed_jtag, 1, 0); + } + + switch (aspeed_jtag->status) { + case JTAG_STATE_IDLE: + aspeed_jtag_run_idle(aspeed_jtag, runtest); + break; + + case JTAG_STATE_PAUSEIR: + /* Fall-through */ + case JTAG_STATE_PAUSEDR: + aspeed_jtag_run_pause(aspeed_jtag, runtest); + break; + + default: + dev_err(aspeed_jtag->dev, "aspeed_jtag_run_test_idle error\n"); + break; + } + + /* Stay on IDLE for at least TCK cycle */ + for (i = 0; i < runtest->tck; i++) + aspeed_jtag_tck_cycle(aspeed_jtag, 0, 0); +} + +static int aspeed_jtag_idle(struct jtag *jtag, + struct jtag_run_test_idle *runtest) +{ + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + + dev_dbg(aspeed_jtag->dev, "runtest, status:%d, mode:%s, state:%s, reset:%d, tck:%d\n", + aspeed_jtag->status, + aspeed_jtag->mode & JTAG_XFER_HW_MODE ? "HW" : "SW", + end_status_str[runtest->endstate], runtest->reset, + runtest->tck); + + if (!(aspeed_jtag->mode & JTAG_XFER_HW_MODE)) { + aspeed_jtag_run_test_idle_sw(aspeed_jtag, runtest); + return 0; + } + + /* Disable sw mode */ + aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); + /* x TMS high + 1 TMS low */ + if (runtest->reset) + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_CTL_ENG_EN | + ASPEED_JTAG_CTL_ENG_OUT_EN | + ASPEED_JTAG_CTL_FORCE_TMS, ASPEED_JTAG_CTRL); + else + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_EC_GO_IDLE, + ASPEED_JTAG_EC); + + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_TDIO, ASPEED_JTAG_SW); + + aspeed_jtag->status = JTAG_STATE_IDLE; + return 0; +} + +static void aspeed_jtag_xfer_sw(struct aspeed_jtag *aspeed_jtag, + struct jtag_xfer *xfer, u32 *data) +{ + static const u8 sm_update_shiftir[] = { 1, 1, 0, 0 }; + static const u8 sm_update_shiftdr[] = { 1, 0, 0 }; + static const u8 sm_pause_idle[] = { 1, 1, 0 }; + static const u8 sm_pause_update[] = { 1, 1 }; + unsigned long remain_xfer = xfer->length; + unsigned long shift_bits = 0; + unsigned long index = 0; + unsigned long tdi; + char tdo; + + if (aspeed_jtag->status != JTAG_STATE_IDLE) { + /*IR/DR Pause->Exit2 IR / DR->Update IR /DR */ + aspeed_jtag_sm_cycle(aspeed_jtag, sm_pause_update, + sizeof(sm_pause_update)); + } + + if (xfer->type == JTAG_SIR_XFER) + /* ->IRSCan->CapIR->ShiftIR */ + aspeed_jtag_sm_cycle(aspeed_jtag, sm_update_shiftir, + sizeof(sm_update_shiftir)); + else + /* ->DRScan->DRCap->DRShift */ + aspeed_jtag_sm_cycle(aspeed_jtag, sm_update_shiftdr, + sizeof(sm_update_shiftdr)); + + tdi = ASPEED_JTAG_GET_TDI(xfer->direction, data[index]); + + 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]); + } + } + + tdo = aspeed_jtag_tck_cycle(aspeed_jtag, 1, tdi & ASPEED_JTAG_DATA_MSB); + data[index] |= tdo << (shift_bits % ASPEED_JTAG_DATA_CHUNK_SIZE); + + /* DIPause/DRPause */ + aspeed_jtag_tck_cycle(aspeed_jtag, 0, 0); + + if (xfer->endstate == JTAG_STATE_IDLE) { + /* ->DRExit2->DRUpdate->IDLE */ + aspeed_jtag_sm_cycle(aspeed_jtag, sm_pause_idle, + sizeof(sm_pause_idle)); + } +} + +static void aspeed_jtag_xfer_push_data(struct aspeed_jtag *aspeed_jtag, + enum jtag_xfer_type type, u32 bits_len) +{ + 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_DOUT_LEN(bits_len) | + ASPEED_JTAG_CTL_INST_EN, ASPEED_JTAG_CTRL); + } 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); + } +} + +static void aspeed_jtag_xfer_push_data_last(struct aspeed_jtag *aspeed_jtag, + enum jtag_xfer_type type, + u32 shift_bits, + enum jtag_endstate endstate) +{ + if (endstate == JTAG_STATE_IDLE) { + if (type == JTAG_SIR_XFER) { + dev_dbg(aspeed_jtag->dev, "IR Keep Pause\n"); + + aspeed_jtag_write(aspeed_jtag, + ASPEED_JTAG_IOUT_LEN(shift_bits), + ASPEED_JTAG_CTRL); + aspeed_jtag_write(aspeed_jtag, + ASPEED_JTAG_IOUT_LEN(shift_bits) | + ASPEED_JTAG_CTL_INST_EN, + ASPEED_JTAG_CTRL); + aspeed_jtag_wait_instruction_pause(aspeed_jtag); + } else { + dev_dbg(aspeed_jtag->dev, "DR Keep Pause\n"); + aspeed_jtag_write(aspeed_jtag, + ASPEED_JTAG_DOUT_LEN(shift_bits) | + ASPEED_JTAG_CTL_DR_UPDATE, + ASPEED_JTAG_CTRL); + aspeed_jtag_write(aspeed_jtag, + ASPEED_JTAG_DOUT_LEN(shift_bits) | + ASPEED_JTAG_CTL_DR_UPDATE | + ASPEED_JTAG_CTL_DATA_EN, + ASPEED_JTAG_CTRL); + aspeed_jtag_wait_data_pause_complete(aspeed_jtag); + } + } else { + if (type == JTAG_SIR_XFER) { + dev_dbg(aspeed_jtag->dev, "IR go IDLE\n"); + + 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); + aspeed_jtag_wait_instruction_complete(aspeed_jtag); + } else { + dev_dbg(aspeed_jtag->dev, "DR go IDLE\n"); + + 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); + aspeed_jtag_wait_data_complete(aspeed_jtag); + } + } +} + +static void 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; + + data_reg = xfer->type == JTAG_SIR_XFER ? + ASPEED_JTAG_INST : ASPEED_JTAG_DATA; + 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) { + shift_bits = ASPEED_JTAG_DATA_CHUNK_SIZE; + + /* + * Read bytes were not equals to column length + * and go to Pause-DR + */ + aspeed_jtag_xfer_push_data(aspeed_jtag, xfer->type, + shift_bits); + } else { + /* + * Read bytes equals to column length => + * Update-DR + */ + shift_bits = remain_xfer; + aspeed_jtag_xfer_push_data_last(aspeed_jtag, xfer->type, + shift_bits, + xfer->endstate); + } + + 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++; + } +} + +static int aspeed_jtag_xfer(struct jtag *jtag, struct jtag_xfer *xfer, + u8 *xfer_data) +{ + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + + dev_dbg(aspeed_jtag->dev, " %s %s xfer, mode:%s, END:%d, len:%d\n", + xfer->type == JTAG_SIR_XFER ? "SIR" : "SDR", + xfer->direction == JTAG_READ_XFER ? "READ" : "WRITE", + aspeed_jtag->mode & JTAG_XFER_HW_MODE ? "HW" : "SW", + xfer->endstate, xfer->length); + + 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); + aspeed_jtag_xfer_hw(aspeed_jtag, xfer, (u32 *)xfer_data); + } + + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_TDIO, ASPEED_JTAG_SW); + 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; +} + +static irqreturn_t aspeed_jtag_interrupt(s32 this_irq, void *dev_id) +{ + struct aspeed_jtag *aspeed_jtag = dev_id; + irqreturn_t ret; + 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; +} + +static int aspeed_jtag_init(struct platform_device *pdev, + struct aspeed_jtag *aspeed_jtag) +{ + struct resource *res; + int err; + + 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; + + 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); + } + + aspeed_jtag->irq = platform_get_irq(pdev, 0); + if (aspeed_jtag->irq < 0) { + dev_err(aspeed_jtag->dev, "no irq specified\n"); + return -ENOENT; + } + + 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); + + /* Enable clock */ + 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_TDIO, ASPEED_JTAG_SW); + + 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"); + goto clk_unprep; + } + + 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); + + aspeed_jtag->flag = 0; + aspeed_jtag->mode = 0; + init_waitqueue_head(&aspeed_jtag->jtag_wq); + return 0; + +clk_unprep: + clk_disable_unprepare(aspeed_jtag->pclk); + return err; +} + +static int aspeed_jtag_deinit(struct platform_device *pdev, + struct aspeed_jtag *aspeed_jtag) +{ + 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, + .idle = aspeed_jtag_idle, + .xfer = aspeed_jtag_xfer, + .mode_set = aspeed_jtag_mode_set +}; + +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; + + 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", }, + {} +}; + +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");