| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright(C) 2016 Linaro Limited. All rights reserved. |
| * Author: Mathieu Poirier <mathieu.poirier@linaro.org> |
| */ |
| |
| #include <linux/coresight.h> |
| #include <linux/dma-mapping.h> |
| #include "coresight-priv.h" |
| #include "coresight-tmc.h" |
| |
| static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata) |
| { |
| u32 axictl, sts; |
| |
| /* Zero out the memory to help with debug */ |
| memset(drvdata->vaddr, 0, drvdata->size); |
| |
| CS_UNLOCK(drvdata->base); |
| |
| /* Wait for TMCSReady bit to be set */ |
| tmc_wait_for_tmcready(drvdata); |
| |
| writel_relaxed(drvdata->size / 4, drvdata->base + TMC_RSZ); |
| writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE); |
| |
| axictl = readl_relaxed(drvdata->base + TMC_AXICTL); |
| axictl &= ~TMC_AXICTL_CLEAR_MASK; |
| axictl |= (TMC_AXICTL_PROT_CTL_B1 | TMC_AXICTL_WR_BURST_16); |
| axictl |= TMC_AXICTL_AXCACHE_OS; |
| |
| if (tmc_etr_has_cap(drvdata, TMC_ETR_AXI_ARCACHE)) { |
| axictl &= ~TMC_AXICTL_ARCACHE_MASK; |
| axictl |= TMC_AXICTL_ARCACHE_OS; |
| } |
| |
| writel_relaxed(axictl, drvdata->base + TMC_AXICTL); |
| tmc_write_dba(drvdata, drvdata->paddr); |
| /* |
| * If the TMC pointers must be programmed before the session, |
| * we have to set it properly (i.e, RRP/RWP to base address and |
| * STS to "not full"). |
| */ |
| if (tmc_etr_has_cap(drvdata, TMC_ETR_SAVE_RESTORE)) { |
| tmc_write_rrp(drvdata, drvdata->paddr); |
| tmc_write_rwp(drvdata, drvdata->paddr); |
| sts = readl_relaxed(drvdata->base + TMC_STS) & ~TMC_STS_FULL; |
| writel_relaxed(sts, drvdata->base + TMC_STS); |
| } |
| |
| writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI | |
| TMC_FFCR_FON_FLIN | TMC_FFCR_FON_TRIG_EVT | |
| TMC_FFCR_TRIGON_TRIGIN, |
| drvdata->base + TMC_FFCR); |
| writel_relaxed(drvdata->trigger_cntr, drvdata->base + TMC_TRG); |
| tmc_enable_hw(drvdata); |
| |
| CS_LOCK(drvdata->base); |
| } |
| |
| static void tmc_etr_dump_hw(struct tmc_drvdata *drvdata) |
| { |
| const u32 *barrier; |
| u32 val; |
| u32 *temp; |
| u64 rwp; |
| |
| rwp = tmc_read_rwp(drvdata); |
| val = readl_relaxed(drvdata->base + TMC_STS); |
| |
| /* |
| * Adjust the buffer to point to the beginning of the trace data |
| * and update the available trace data. |
| */ |
| if (val & TMC_STS_FULL) { |
| drvdata->buf = drvdata->vaddr + rwp - drvdata->paddr; |
| drvdata->len = drvdata->size; |
| |
| barrier = barrier_pkt; |
| temp = (u32 *)drvdata->buf; |
| |
| while (*barrier) { |
| *temp = *barrier; |
| temp++; |
| barrier++; |
| } |
| |
| } else { |
| drvdata->buf = drvdata->vaddr; |
| drvdata->len = rwp - drvdata->paddr; |
| } |
| } |
| |
| static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata) |
| { |
| CS_UNLOCK(drvdata->base); |
| |
| tmc_flush_and_stop(drvdata); |
| /* |
| * When operating in sysFS mode the content of the buffer needs to be |
| * read before the TMC is disabled. |
| */ |
| if (drvdata->mode == CS_MODE_SYSFS) |
| tmc_etr_dump_hw(drvdata); |
| tmc_disable_hw(drvdata); |
| |
| CS_LOCK(drvdata->base); |
| } |
| |
| static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev) |
| { |
| int ret = 0; |
| bool used = false; |
| unsigned long flags; |
| void __iomem *vaddr = NULL; |
| dma_addr_t paddr = 0; |
| struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); |
| |
| /* |
| * If we don't have a buffer release the lock and allocate memory. |
| * Otherwise keep the lock and move along. |
| */ |
| spin_lock_irqsave(&drvdata->spinlock, flags); |
| if (!drvdata->vaddr) { |
| spin_unlock_irqrestore(&drvdata->spinlock, flags); |
| |
| /* |
| * Contiguous memory can't be allocated while a spinlock is |
| * held. As such allocate memory here and free it if a buffer |
| * has already been allocated (from a previous session). |
| */ |
| vaddr = dma_alloc_coherent(drvdata->dev, drvdata->size, |
| &paddr, GFP_KERNEL); |
| if (!vaddr) |
| return -ENOMEM; |
| |
| /* Let's try again */ |
| spin_lock_irqsave(&drvdata->spinlock, flags); |
| } |
| |
| if (drvdata->reading) { |
| ret = -EBUSY; |
| goto out; |
| } |
| |
| /* |
| * In sysFS mode we can have multiple writers per sink. Since this |
| * sink is already enabled no memory is needed and the HW need not be |
| * touched. |
| */ |
| if (drvdata->mode == CS_MODE_SYSFS) |
| goto out; |
| |
| /* |
| * If drvdata::vaddr == NULL, use the memory allocated above. |
| * Otherwise a buffer still exists from a previous session, so |
| * simply use that. |
| */ |
| if (drvdata->vaddr == NULL) { |
| used = true; |
| drvdata->vaddr = vaddr; |
| drvdata->paddr = paddr; |
| drvdata->buf = drvdata->vaddr; |
| } |
| |
| drvdata->mode = CS_MODE_SYSFS; |
| tmc_etr_enable_hw(drvdata); |
| out: |
| spin_unlock_irqrestore(&drvdata->spinlock, flags); |
| |
| /* Free memory outside the spinlock if need be */ |
| if (!used && vaddr) |
| dma_free_coherent(drvdata->dev, drvdata->size, vaddr, paddr); |
| |
| if (!ret) |
| dev_info(drvdata->dev, "TMC-ETR enabled\n"); |
| |
| return ret; |
| } |
| |
| static int tmc_enable_etr_sink_perf(struct coresight_device *csdev) |
| { |
| int ret = 0; |
| unsigned long flags; |
| struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); |
| |
| spin_lock_irqsave(&drvdata->spinlock, flags); |
| if (drvdata->reading) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| /* |
| * In Perf mode there can be only one writer per sink. There |
| * is also no need to continue if the ETR is already operated |
| * from sysFS. |
| */ |
| if (drvdata->mode != CS_MODE_DISABLED) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| drvdata->mode = CS_MODE_PERF; |
| tmc_etr_enable_hw(drvdata); |
| out: |
| spin_unlock_irqrestore(&drvdata->spinlock, flags); |
| |
| return ret; |
| } |
| |
| static int tmc_enable_etr_sink(struct coresight_device *csdev, u32 mode) |
| { |
| switch (mode) { |
| case CS_MODE_SYSFS: |
| return tmc_enable_etr_sink_sysfs(csdev); |
| case CS_MODE_PERF: |
| return tmc_enable_etr_sink_perf(csdev); |
| } |
| |
| /* We shouldn't be here */ |
| return -EINVAL; |
| } |
| |
| static void tmc_disable_etr_sink(struct coresight_device *csdev) |
| { |
| unsigned long flags; |
| struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); |
| |
| spin_lock_irqsave(&drvdata->spinlock, flags); |
| if (drvdata->reading) { |
| spin_unlock_irqrestore(&drvdata->spinlock, flags); |
| return; |
| } |
| |
| /* Disable the TMC only if it needs to */ |
| if (drvdata->mode != CS_MODE_DISABLED) { |
| tmc_etr_disable_hw(drvdata); |
| drvdata->mode = CS_MODE_DISABLED; |
| } |
| |
| spin_unlock_irqrestore(&drvdata->spinlock, flags); |
| |
| dev_info(drvdata->dev, "TMC-ETR disabled\n"); |
| } |
| |
| static const struct coresight_ops_sink tmc_etr_sink_ops = { |
| .enable = tmc_enable_etr_sink, |
| .disable = tmc_disable_etr_sink, |
| }; |
| |
| const struct coresight_ops tmc_etr_cs_ops = { |
| .sink_ops = &tmc_etr_sink_ops, |
| }; |
| |
| int tmc_read_prepare_etr(struct tmc_drvdata *drvdata) |
| { |
| int ret = 0; |
| unsigned long flags; |
| |
| /* config types are set a boot time and never change */ |
| if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETR)) |
| return -EINVAL; |
| |
| spin_lock_irqsave(&drvdata->spinlock, flags); |
| if (drvdata->reading) { |
| ret = -EBUSY; |
| goto out; |
| } |
| |
| /* Don't interfere if operated from Perf */ |
| if (drvdata->mode == CS_MODE_PERF) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| /* If drvdata::buf is NULL the trace data has been read already */ |
| if (drvdata->buf == NULL) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| /* Disable the TMC if need be */ |
| if (drvdata->mode == CS_MODE_SYSFS) |
| tmc_etr_disable_hw(drvdata); |
| |
| drvdata->reading = true; |
| out: |
| spin_unlock_irqrestore(&drvdata->spinlock, flags); |
| |
| return ret; |
| } |
| |
| int tmc_read_unprepare_etr(struct tmc_drvdata *drvdata) |
| { |
| unsigned long flags; |
| dma_addr_t paddr; |
| void __iomem *vaddr = NULL; |
| |
| /* config types are set a boot time and never change */ |
| if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETR)) |
| return -EINVAL; |
| |
| spin_lock_irqsave(&drvdata->spinlock, flags); |
| |
| /* RE-enable the TMC if need be */ |
| if (drvdata->mode == CS_MODE_SYSFS) { |
| /* |
| * The trace run will continue with the same allocated trace |
| * buffer. The trace buffer is cleared in tmc_etr_enable_hw(), |
| * so we don't have to explicitly clear it. Also, since the |
| * tracer is still enabled drvdata::buf can't be NULL. |
| */ |
| tmc_etr_enable_hw(drvdata); |
| } else { |
| /* |
| * The ETR is not tracing and the buffer was just read. |
| * As such prepare to free the trace buffer. |
| */ |
| vaddr = drvdata->vaddr; |
| paddr = drvdata->paddr; |
| drvdata->buf = drvdata->vaddr = NULL; |
| } |
| |
| drvdata->reading = false; |
| spin_unlock_irqrestore(&drvdata->spinlock, flags); |
| |
| /* Free allocated memory out side of the spinlock */ |
| if (vaddr) |
| dma_free_coherent(drvdata->dev, drvdata->size, vaddr, paddr); |
| |
| return 0; |
| } |