Message ID | 1501679918-20486-3-git-send-email-oleksandrs@mellanox.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On 08/02/2017 03:18 PM, Oleksandr Shamray wrote: > 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 <oleksandrs@mellanox.com> > Signed-off-by: Jiri Pirko <jiri@mellanox.com> > --- > drivers/jtag/Kconfig | 13 + > drivers/jtag/Makefile | 1 + > drivers/jtag/jtag-aspeed.c | 802 ++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 816 insertions(+), 0 deletions(-) > create mode 100644 drivers/jtag/jtag-aspeed.c > > diff --git a/drivers/jtag/Kconfig b/drivers/jtag/Kconfig > index a8d0149..7bf709c 100644 > --- a/drivers/jtag/Kconfig > +++ b/drivers/jtag/Kconfig > @@ -16,3 +16,16 @@ 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 > + ---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 aspeed_jtag. > + > diff --git a/drivers/jtag/Makefile b/drivers/jtag/Makefile > index e811330..e9fa7fa 100644 > --- a/drivers/jtag/Makefile > +++ b/drivers/jtag/Makefile > @@ -1,2 +1,3 @@ > 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..b820824 > --- /dev/null > +++ b/drivers/jtag/jtag-aspeed.c > @@ -0,0 +1,802 @@ > +/* > + * Copyright (c) 2017 Mellanox Technologies. All rights reserved. > + * Copyright (c) 2017 Oleksandr Shamray <oleksandrs@mellanox.com> > + * > + * Redistribution and use in source and binary forms, with or without > + * modification, are permitted provided that the following conditions are met: > + * > + * 1. Redistributions of source code must retain the above copyright > + * notice, this list of conditions and the following disclaimer. > + * 2. Redistributions in binary form must reproduce the above copyright > + * notice, this list of conditions and the following disclaimer in the > + * documentation and/or other materials provided with the distribution. > + * 3. Neither the names of the copyright holders nor the names of its > + * contributors may be used to endorse or promote products derived from > + * this software without specific prior written permission. > + * > + * Alternatively, this software may be distributed under the terms of the > + * GNU General Public License ("GPL") version 2 as published by the Free > + * Software Foundation. > + * > + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" > + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE > + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE > + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE > + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR > + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF > + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS > + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN > + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) > + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE > + * POSSIBILITY OF SUCH DAMAGE. > + */ Please use SPDX-License-Identifier here aswell. > + > +#include <asm/mach-types.h> > +#include <asm/mach/arch.h> > +#include <linux/clk.h> > +#include <linux/device.h> > +#include <linux/interrupt.h> > +#include <linux/jtag.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/of_address.h> > +#include <linux/platform_device.h> > +#include <linux/slab.h> > +#include <uapi/linux/jtag.h> > + > +#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_TCK_WAIT 10 > +#define ASPEED_JTAG_RESET_CNTR 10 > + > +#define ASPEED_JTAG_NAME "jtag-aspeed" > + > +static int aspeed_jtag_freq_set(struct jtag *jtag, unsigned long freq); > +static int aspeed_jtag_freq_get(struct jtag *jtag, unsigned long *frq); > +static int aspeed_jtag_status_get(struct jtag *jtag, > + enum jtag_endstate *status); > +static int aspeed_jtag_idle(struct jtag *jtag, > + struct jtag_run_test_idle *runtest); > +static int aspeed_jtag_xfer(struct jtag *jtag, struct jtag_xfer *xfer); > + > +struct aspeed_jtag { > + void __iomem *reg_base; > + struct device *dev; > + struct clk *pclk; > + enum jtag_endstate status; > + int irq; > + u32 flag; > + wait_queue_head_t jtag_wq; > + bool is_open; > +}; > + > +static char *end_status_str[] = {"idle", "ir pause", "drpause"}; > + > +static 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 > +}; > + > +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); > +} Maybe readl_relaxed/writel_relaxed would be enough here. > + > +static int aspeed_jtag_freq_set(struct jtag *jtag, unsigned long freq) > +{ > + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); > + u16 div; > + u32 tck_val; > + unsigned long apb_frq; > + > + apb_frq = clk_get_rate(aspeed_jtag->pclk); > + div = (apb_frq % freq == 0) ? (apb_frq / freq) - 1 : (apb_frq / 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, unsigned long *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 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); > + > + 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, 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_test_idle_sw(struct aspeed_jtag *aspeed_jtag, > + struct jtag_run_test_idle *runtest) > +{ > + char sm_pause_irpause[] = {1, 1, 1, 1, 0, 1, 0}; > + char sm_pause_drpause[] = {1, 1, 1, 0, 1, 0}; > + char sm_idle_irpause[] = {1, 1, 0, 1, 0}; > + char sm_idle_drpause[] = {1, 0, 1, 0}; > + char sm_pause_idle[] = {1, 1, 0}; > + 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: > + 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; > + } > + break; > + > + case JTAG_STATE_PAUSEIR: > + /* Fall-through */ > + case JTAG_STATE_PAUSEDR: > + /* From IR/DR Pause */ > + 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; > + } > + 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); > +} > + > +/** > + * aspeed_jtag_run_test_idle: > + * JTAG reset: generates at least 9 TMS high and 1 TMS low to force > + * devices into Run-Test/Idle State. > + */ > +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, "aspeed_jtag runtest, status:%d, mode:%s, state:%s, reset:%d, tck:%d\n", > + aspeed_jtag->status, runtest->mode ? "SW" : "HW", > + end_status_str[runtest->endstate], runtest->reset, > + runtest->tck); > + > + if (runtest->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_MODE_EN | > + ASPEED_JTAG_SW_MODE_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, char *tdio_data) > +{ > + unsigned long remain_xfer = xfer->length; > + unsigned long shift_bits = 0; > + unsigned long index = 0; > + unsigned long tdi; > + char tdo; > + unsigned long *data = (unsigned long *)tdio_data; > + > + if (xfer->direction == JTAG_READ_XFER) > + tdi = UINT_MAX; > + else > + tdi = 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) { > + dev_dbg(aspeed_jtag->dev, "R/W data[%lu]:%lx\n", > + index, data[index]); > + > + tdo = 0; > + index++; > + > + if (xfer->direction == JTAG_READ_XFER) > + tdi = UINT_MAX; > + else > + tdi = 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); > +} > + > +static void aspeed_jtag_xfer_push_data(struct aspeed_jtag *aspeed_jtag, > + enum jtag_xfer_type type, u32 bits_len) > +{ > + dev_dbg(aspeed_jtag->dev, "shift bits %d\n", 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, char *tdio_data) > +{ > + unsigned long remain_xfer = xfer->length; > + unsigned long *data = (unsigned long *)tdio_data; > + unsigned long shift_bits; > + unsigned long index = 0; > + 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) { > + dev_dbg(aspeed_jtag->dev, "W dr->dr_data[%lu]:%lx\n", > + index, data[index]); > + > + 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); > + dev_dbg(aspeed_jtag->dev, "R dr->dr_data[%lu]:%lx\n", > + index, data[index]); > + } > + > + remain_xfer = remain_xfer - shift_bits; > + index++; > + dev_dbg(aspeed_jtag->dev, "remain_xfer %lu\n", remain_xfer); > + } > +} > + > +static int aspeed_jtag_xfer(struct jtag *jtag, struct jtag_xfer *xfer) > +{ > + unsigned long remain_xfer = xfer->length; > + unsigned long *data = (unsigned long *)xfer->tdio; > + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); > + char sm_update_shiftir[] = {1, 1, 0, 0}; > + char sm_update_shiftdr[] = {1, 0, 0}; > + char sm_pause_idle[] = {1, 1, 0}; > + char sm_pause_update[] = {1, 1}; > + unsigned long offset; > + char dbg_str[256]; > + int pos = 0; > + int i; > + > + for (offset = 0, i = 0; offset < xfer->length; > + offset += ASPEED_JTAG_DATA_CHUNK_SIZE, i++) { > + pos += snprintf(&dbg_str[pos], sizeof(dbg_str) - pos, > + "0x%08lx ", data[i]); > + } > + > + dev_dbg(aspeed_jtag->dev, "aspeed_jtag %s %s xfer, mode:%s, END:%d, len:%lu, TDI[%s]\n", > + xfer->type == JTAG_SIR_XFER ? "SIR" : "SDR", > + xfer->direction == JTAG_READ_XFER ? "READ" : "WRITE", > + xfer->mode ? "SW" : "HW", > + xfer->endstate, remain_xfer, dbg_str); > + > + if (xfer->mode == JTAG_XFER_SW_MODE) { > + /* SW mode */ > + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_MODE_EN | > + ASPEED_JTAG_SW_MODE_TDIO, ASPEED_JTAG_SW); > + > + 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)); > + > + aspeed_jtag_xfer_sw(aspeed_jtag, xfer, xfer->tdio); > + > + /* 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)); > + } > + } else { > + /* hw mode */ > + aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); > + aspeed_jtag_xfer_hw(aspeed_jtag, xfer, xfer->tdio); > + } > + > + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_MODE_EN | > + ASPEED_JTAG_SW_MODE_TDIO, ASPEED_JTAG_SW); > + aspeed_jtag->status = xfer->endstate; > + return 0; > +} > + > +static int aspeed_jtag_status_get(struct jtag *jtag, enum jtag_endstate *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; > + u32 status; > + irqreturn_t ret; > + > + status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR); > + dev_dbg(aspeed_jtag->dev, "status %x\n", status); > + > + 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, "aspeed_jtag irq status:%x\n", > + status); > + ret = IRQ_NONE; > + } > + return ret; > +} > + > +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)) { > + err = -ENOMEM; > + goto out_region; > + } > + > + 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); > + } clk_prepare_enable ? > + > + aspeed_jtag->irq = platform_get_irq(pdev, 0); > + if (aspeed_jtag->irq < 0) { > + dev_err(aspeed_jtag->dev, "no irq specified\n"); > + err = -ENOENT; > + goto out_region; > + } > + > + /* 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_MODE_EN | > + ASPEED_JTAG_SW_MODE_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, "aspeed_jtag unable to get IRQ"); > + goto out_region; > + } > + dev_dbg(&pdev->dev, "aspeed_jtag:IRQ %d.\n", aspeed_jtag->irq); > + > + 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; > + init_waitqueue_head(&aspeed_jtag->jtag_wq); > + return 0; > + > +out_region: > + release_mem_region(res->start, res->end - res->start + 1); > + return err; > +} > + > +int aspeed_jtag_deinit(struct platform_device *pdev, > + struct aspeed_jtag *aspeed_jtag) > +{ > + aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_ISR); > + devm_free_irq(aspeed_jtag->dev, aspeed_jtag->irq, aspeed_jtag); > + /* Disabe clock */ > + aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_CTRL); clk_prepare_disable ? > + return 0; > +} > + > +static int aspeed_jtag_probe(struct platform_device *pdev) > +{ > + struct aspeed_jtag *aspeed_jtag; > + struct jtag *jtag; > + int err; > + > + if (!of_device_is_compatible(pdev->dev.of_node, "aspeed,aspeed-jtag")) > + return -ENOMEM; > + > + jtag = jtag_alloc(sizeof(*aspeed_jtag), &aspeed_jtag_ops); > + if (!jtag) > + return -ENODEV; > + > + 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 = jtag_register(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; > + > + jtag = platform_get_drvdata(pdev); > + aspeed_jtag_deinit(pdev, jtag_priv(jtag)); > + jtag_unregister(jtag); > + jtag_free(jtag); > + return 0; > +} > + > +static const struct of_device_id aspeed_jtag_of_match[] = { > + { .compatible = "aspeed,aspeed-jtag", }, Please use soc-specific compatible name. > + {} > +}; > + > +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 <oleksandrs@mellanox.com>"); > +MODULE_DESCRIPTION("ASPEED JTAG driver"); > +MODULE_LICENSE("Dual BSD/GPL"); > Hi Oleksandr, Great work, but you forgot to add proper dt-bindings for the driver. Neil
On Wed, Aug 2, 2017 at 3:18 PM, Oleksandr Shamray <oleksandrs@mellanox.com> wrote: > 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 <oleksandrs@mellanox.com> > Signed-off-by: Jiri Pirko <jiri@mellanox.com> Looking at this one before the subsystem. Overall looks really nice, it seems you got a good abstraction between the subsystem and the driver. > + > +static int aspeed_jtag_freq_set(struct jtag *jtag, unsigned long freq); > +static int aspeed_jtag_freq_get(struct jtag *jtag, unsigned long *frq); > +static int aspeed_jtag_status_get(struct jtag *jtag, > + enum jtag_endstate *status); > +static int aspeed_jtag_idle(struct jtag *jtag, > + struct jtag_run_test_idle *runtest); > +static int aspeed_jtag_xfer(struct jtag *jtag, struct jtag_xfer *xfer); Please try to reorder the functions definitions in a way that lets you remove the forward declarations. > + > +static void aspeed_jtag_run_test_idle_sw(struct aspeed_jtag *aspeed_jtag, > + struct jtag_run_test_idle *runtest) > +{ > + char sm_pause_irpause[] = {1, 1, 1, 1, 0, 1, 0}; > + char sm_pause_drpause[] = {1, 1, 1, 0, 1, 0}; > + char sm_idle_irpause[] = {1, 1, 0, 1, 0}; > + char sm_idle_drpause[] = {1, 0, 1, 0}; > + char sm_pause_idle[] = {1, 1, 0}; These could be 'static const' if you adapt the aspeed_jtag_sm_cycle prototype accordingly. > + > +static const struct of_device_id aspeed_jtag_of_match[] = { > + { .compatible = "aspeed,aspeed-jtag", }, > + {} > +}; The series should include a patch for the DT binding for this device. You may want to be a little more specific here, to avoid problems if aspeed ever makes an updated version of this device with a slightly different register interface. Usually we include the full name of the SoC in the "compatible" string for that. Arnd
On Wed, Aug 2, 2017 at 4:30 PM, Neil Armstrong <narmstrong@baylibre.com> wrote: > On 08/02/2017 03:18 PM, Oleksandr Shamray wrote: >> Driver adds support of Aspeed 2500/2400 series SOC JTAG master controller. >> +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); >> +} > > Maybe readl_relaxed/writel_relaxed would be enough here. I'd prefer keeping the regular accessors here, unless this is shown to be a performance bottleneck, and there is a comment to explain how the relaxed accessors are determined to be safe. Arnd
On 08/02/2017 06:18 AM, Oleksandr Shamray wrote: > > diff --git a/drivers/jtag/Kconfig b/drivers/jtag/Kconfig > index a8d0149..7bf709c 100644 > --- a/drivers/jtag/Kconfig > +++ b/drivers/jtag/Kconfig > @@ -16,3 +16,16 @@ 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 > + ---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 aspeed_jtag. In the Makefile, it looks like it is called jtag-aspeed. > + > diff --git a/drivers/jtag/Makefile b/drivers/jtag/Makefile > index e811330..e9fa7fa 100644 > --- a/drivers/jtag/Makefile > +++ b/drivers/jtag/Makefile > @@ -1,2 +1,3 @@ > obj-$(CONFIG_JTAG) += jtag.o > +obj-$(CONFIG_JTAG_ASPEED) += jtag-aspeed.o
Hi Oleksandr, [auto build test ERROR on linus/master] [also build test ERROR on v4.13-rc3 next-20170802] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system] url: https://github.com/0day-ci/linux/commits/Oleksandr-Shamray/JTAG-driver-introduction/20170803-110721 config: arm64-allmodconfig (attached as .config) compiler: aarch64-linux-gnu-gcc (Debian 6.1.1-9) 6.1.1 20160705 reproduce: wget https://raw.githubusercontent.com/01org/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # save the attached .config to linux build tree make.cross ARCH=arm64 All errors (new ones prefixed by >>): >> drivers/jtag/jtag-aspeed.c:34:28: fatal error: asm/mach-types.h: No such file or directory #include <asm/mach-types.h> ^ compilation terminated. vim +34 drivers/jtag/jtag-aspeed.c > 34 #include <asm/mach-types.h> 35 #include <asm/mach/arch.h> 36 #include <linux/clk.h> 37 #include <linux/device.h> 38 #include <linux/interrupt.h> 39 #include <linux/jtag.h> 40 #include <linux/kernel.h> 41 #include <linux/module.h> 42 #include <linux/of_address.h> 43 #include <linux/platform_device.h> 44 #include <linux/slab.h> 45 #include <uapi/linux/jtag.h> 46 --- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation
Hi Oleksandr,
[auto build test WARNING on linus/master]
[also build test WARNING on v4.13-rc3 next-20170803]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Oleksandr-Shamray/JTAG-driver-introduction/20170803-110721
coccinelle warnings: (new ones prefixed by >>)
>> drivers/jtag/jtag-aspeed.c:724:37-40: ERROR: Missing resource_size with res
Please review and possibly fold the followup patch.
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
diff --git a/drivers/jtag/Kconfig b/drivers/jtag/Kconfig index a8d0149..7bf709c 100644 --- a/drivers/jtag/Kconfig +++ b/drivers/jtag/Kconfig @@ -16,3 +16,16 @@ 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 + ---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 aspeed_jtag. + diff --git a/drivers/jtag/Makefile b/drivers/jtag/Makefile index e811330..e9fa7fa 100644 --- a/drivers/jtag/Makefile +++ b/drivers/jtag/Makefile @@ -1,2 +1,3 @@ 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..b820824 --- /dev/null +++ b/drivers/jtag/jtag-aspeed.c @@ -0,0 +1,802 @@ +/* + * Copyright (c) 2017 Mellanox Technologies. All rights reserved. + * Copyright (c) 2017 Oleksandr Shamray <oleksandrs@mellanox.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <asm/mach-types.h> +#include <asm/mach/arch.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/jtag.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <uapi/linux/jtag.h> + +#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_TCK_WAIT 10 +#define ASPEED_JTAG_RESET_CNTR 10 + +#define ASPEED_JTAG_NAME "jtag-aspeed" + +static int aspeed_jtag_freq_set(struct jtag *jtag, unsigned long freq); +static int aspeed_jtag_freq_get(struct jtag *jtag, unsigned long *frq); +static int aspeed_jtag_status_get(struct jtag *jtag, + enum jtag_endstate *status); +static int aspeed_jtag_idle(struct jtag *jtag, + struct jtag_run_test_idle *runtest); +static int aspeed_jtag_xfer(struct jtag *jtag, struct jtag_xfer *xfer); + +struct aspeed_jtag { + void __iomem *reg_base; + struct device *dev; + struct clk *pclk; + enum jtag_endstate status; + int irq; + u32 flag; + wait_queue_head_t jtag_wq; + bool is_open; +}; + +static char *end_status_str[] = {"idle", "ir pause", "drpause"}; + +static 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 +}; + +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, unsigned long freq) +{ + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + u16 div; + u32 tck_val; + unsigned long apb_frq; + + apb_frq = clk_get_rate(aspeed_jtag->pclk); + div = (apb_frq % freq == 0) ? (apb_frq / freq) - 1 : (apb_frq / 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, unsigned long *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 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); + + 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, 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_test_idle_sw(struct aspeed_jtag *aspeed_jtag, + struct jtag_run_test_idle *runtest) +{ + char sm_pause_irpause[] = {1, 1, 1, 1, 0, 1, 0}; + char sm_pause_drpause[] = {1, 1, 1, 0, 1, 0}; + char sm_idle_irpause[] = {1, 1, 0, 1, 0}; + char sm_idle_drpause[] = {1, 0, 1, 0}; + char sm_pause_idle[] = {1, 1, 0}; + 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: + 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; + } + break; + + case JTAG_STATE_PAUSEIR: + /* Fall-through */ + case JTAG_STATE_PAUSEDR: + /* From IR/DR Pause */ + 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; + } + 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); +} + +/** + * aspeed_jtag_run_test_idle: + * JTAG reset: generates at least 9 TMS high and 1 TMS low to force + * devices into Run-Test/Idle State. + */ +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, "aspeed_jtag runtest, status:%d, mode:%s, state:%s, reset:%d, tck:%d\n", + aspeed_jtag->status, runtest->mode ? "SW" : "HW", + end_status_str[runtest->endstate], runtest->reset, + runtest->tck); + + if (runtest->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_MODE_EN | + ASPEED_JTAG_SW_MODE_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, char *tdio_data) +{ + unsigned long remain_xfer = xfer->length; + unsigned long shift_bits = 0; + unsigned long index = 0; + unsigned long tdi; + char tdo; + unsigned long *data = (unsigned long *)tdio_data; + + if (xfer->direction == JTAG_READ_XFER) + tdi = UINT_MAX; + else + tdi = 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) { + dev_dbg(aspeed_jtag->dev, "R/W data[%lu]:%lx\n", + index, data[index]); + + tdo = 0; + index++; + + if (xfer->direction == JTAG_READ_XFER) + tdi = UINT_MAX; + else + tdi = 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); +} + +static void aspeed_jtag_xfer_push_data(struct aspeed_jtag *aspeed_jtag, + enum jtag_xfer_type type, u32 bits_len) +{ + dev_dbg(aspeed_jtag->dev, "shift bits %d\n", 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, char *tdio_data) +{ + unsigned long remain_xfer = xfer->length; + unsigned long *data = (unsigned long *)tdio_data; + unsigned long shift_bits; + unsigned long index = 0; + 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) { + dev_dbg(aspeed_jtag->dev, "W dr->dr_data[%lu]:%lx\n", + index, data[index]); + + 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); + dev_dbg(aspeed_jtag->dev, "R dr->dr_data[%lu]:%lx\n", + index, data[index]); + } + + remain_xfer = remain_xfer - shift_bits; + index++; + dev_dbg(aspeed_jtag->dev, "remain_xfer %lu\n", remain_xfer); + } +} + +static int aspeed_jtag_xfer(struct jtag *jtag, struct jtag_xfer *xfer) +{ + unsigned long remain_xfer = xfer->length; + unsigned long *data = (unsigned long *)xfer->tdio; + struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag); + char sm_update_shiftir[] = {1, 1, 0, 0}; + char sm_update_shiftdr[] = {1, 0, 0}; + char sm_pause_idle[] = {1, 1, 0}; + char sm_pause_update[] = {1, 1}; + unsigned long offset; + char dbg_str[256]; + int pos = 0; + int i; + + for (offset = 0, i = 0; offset < xfer->length; + offset += ASPEED_JTAG_DATA_CHUNK_SIZE, i++) { + pos += snprintf(&dbg_str[pos], sizeof(dbg_str) - pos, + "0x%08lx ", data[i]); + } + + dev_dbg(aspeed_jtag->dev, "aspeed_jtag %s %s xfer, mode:%s, END:%d, len:%lu, TDI[%s]\n", + xfer->type == JTAG_SIR_XFER ? "SIR" : "SDR", + xfer->direction == JTAG_READ_XFER ? "READ" : "WRITE", + xfer->mode ? "SW" : "HW", + xfer->endstate, remain_xfer, dbg_str); + + if (xfer->mode == JTAG_XFER_SW_MODE) { + /* SW mode */ + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_MODE_EN | + ASPEED_JTAG_SW_MODE_TDIO, ASPEED_JTAG_SW); + + 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)); + + aspeed_jtag_xfer_sw(aspeed_jtag, xfer, xfer->tdio); + + /* 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)); + } + } else { + /* hw mode */ + aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW); + aspeed_jtag_xfer_hw(aspeed_jtag, xfer, xfer->tdio); + } + + aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_MODE_EN | + ASPEED_JTAG_SW_MODE_TDIO, ASPEED_JTAG_SW); + aspeed_jtag->status = xfer->endstate; + return 0; +} + +static int aspeed_jtag_status_get(struct jtag *jtag, enum jtag_endstate *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; + u32 status; + irqreturn_t ret; + + status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR); + dev_dbg(aspeed_jtag->dev, "status %x\n", status); + + 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, "aspeed_jtag irq status:%x\n", + status); + ret = IRQ_NONE; + } + return ret; +} + +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)) { + err = -ENOMEM; + goto out_region; + } + + 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"); + err = -ENOENT; + goto out_region; + } + + /* 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_MODE_EN | + ASPEED_JTAG_SW_MODE_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, "aspeed_jtag unable to get IRQ"); + goto out_region; + } + dev_dbg(&pdev->dev, "aspeed_jtag:IRQ %d.\n", aspeed_jtag->irq); + + 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; + init_waitqueue_head(&aspeed_jtag->jtag_wq); + return 0; + +out_region: + release_mem_region(res->start, res->end - res->start + 1); + return err; +} + +int aspeed_jtag_deinit(struct platform_device *pdev, + struct aspeed_jtag *aspeed_jtag) +{ + aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_ISR); + devm_free_irq(aspeed_jtag->dev, aspeed_jtag->irq, aspeed_jtag); + /* Disabe clock */ + aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_CTRL); + return 0; +} + +static int aspeed_jtag_probe(struct platform_device *pdev) +{ + struct aspeed_jtag *aspeed_jtag; + struct jtag *jtag; + int err; + + if (!of_device_is_compatible(pdev->dev.of_node, "aspeed,aspeed-jtag")) + return -ENOMEM; + + jtag = jtag_alloc(sizeof(*aspeed_jtag), &aspeed_jtag_ops); + if (!jtag) + return -ENODEV; + + 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 = jtag_register(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; + + jtag = platform_get_drvdata(pdev); + aspeed_jtag_deinit(pdev, jtag_priv(jtag)); + jtag_unregister(jtag); + jtag_free(jtag); + return 0; +} + +static const struct of_device_id aspeed_jtag_of_match[] = { + { .compatible = "aspeed,aspeed-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 <oleksandrs@mellanox.com>"); +MODULE_DESCRIPTION("ASPEED JTAG driver"); +MODULE_LICENSE("Dual BSD/GPL");