diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/hwtracing/coresight/coresight-tmc.c | 382 |
1 files changed, 378 insertions, 4 deletions
diff --git a/drivers/hwtracing/coresight/coresight-tmc.c b/drivers/hwtracing/coresight/coresight-tmc.c index 9b83eb335a34..20077fdfca80 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.c +++ b/drivers/hwtracing/coresight/coresight-tmc.c @@ -25,9 +25,13 @@ #include <linux/spinlock.h> #include <linux/pm_runtime.h> #include <linux/of.h> +#include <linux/of_address.h> #include <linux/coresight.h> #include <linux/amba/bus.h> #include <asm/cacheflush.h> +#include <linux/msm-sps.h> +#include <linux/usb_bam.h> +#include <linux/usb/usb_qdss.h> #include "coresight-priv.h" @@ -86,6 +90,9 @@ #define TMC_ETR_SG_NXT_TBL(phys_pte) (((phys_pte >> PAGE_SHIFT) << 4) | 0x3) #define TMC_ETR_SG_LST_ENT(phys_pte) (((phys_pte >> PAGE_SHIFT) << 4) | 0x1) +#define TMC_ETR_BAM_PIPE_INDEX 0 +#define TMC_ETR_BAM_NR_PIPES 2 + enum tmc_config_type { TMC_CONFIG_TYPE_ETB, TMC_CONFIG_TYPE_ETR, @@ -115,6 +122,31 @@ static const char * const str_tmc_etr_mem_type[] = { [TMC_ETR_MEM_TYPE_SG] = "sg", }; +enum tmc_etr_out_mode { + TMC_ETR_OUT_MODE_NONE, + TMC_ETR_OUT_MODE_MEM, + TMC_ETR_OUT_MODE_USB, +}; + +static const char * const str_tmc_etr_out_mode[] = { + [TMC_ETR_OUT_MODE_NONE] = "none", + [TMC_ETR_OUT_MODE_MEM] = "mem", + [TMC_ETR_OUT_MODE_USB] = "usb", +}; + +struct tmc_etr_bam_data { + struct sps_bam_props props; + unsigned long handle; + struct sps_pipe *pipe; + struct sps_connect connect; + uint32_t src_pipe_idx; + unsigned long dest; + uint32_t dest_pipe_idx; + struct sps_mem_buffer desc_fifo; + struct sps_mem_buffer data_fifo; + bool enable; +}; + /** * struct tmc_drvdata - specifics associated to an TMC component * @base: memory mapped base address for this component. @@ -152,6 +184,10 @@ struct tmc_drvdata { enum tmc_etr_mem_type memtype; u32 delta_bottom; int sg_blk_num; + enum tmc_etr_out_mode out_mode; + struct usb_qdss_ch *usbch; + struct tmc_etr_bam_data *bamdata; + bool enable_to_bam; }; static void tmc_wait_for_ready(struct tmc_drvdata *drvdata) @@ -531,6 +567,175 @@ static void tmc_etf_enable_hw(struct tmc_drvdata *drvdata) CS_LOCK(drvdata->base); } +static void tmc_etr_fill_usb_bam_data(struct tmc_drvdata *drvdata) +{ + struct tmc_etr_bam_data *bamdata = drvdata->bamdata; + + get_qdss_bam_connection_info(&bamdata->dest, + &bamdata->dest_pipe_idx, + &bamdata->src_pipe_idx, + &bamdata->desc_fifo, + &bamdata->data_fifo, + NULL); +} + +static void __tmc_etr_enable_to_bam(struct tmc_drvdata *drvdata) +{ + struct tmc_etr_bam_data *bamdata = drvdata->bamdata; + uint32_t axictl; + + if (drvdata->enable_to_bam) + return; + + /* Configure and enable required CSR registers */ + msm_qdss_csr_enable_bam_to_usb(); + + /* Configure and enable ETR for usb bam output */ + + CS_UNLOCK(drvdata->base); + + writel_relaxed(bamdata->data_fifo.size / 4, drvdata->base + TMC_RSZ); + writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE); + + axictl = readl_relaxed(drvdata->base + TMC_AXICTL); + axictl |= (0xF << 8); + writel_relaxed(axictl, drvdata->base + TMC_AXICTL); + axictl &= ~(0x1 << 7); + writel_relaxed(axictl, drvdata->base + TMC_AXICTL); + axictl = (axictl & ~0x3) | 0x2; + writel_relaxed(axictl, drvdata->base + TMC_AXICTL); + + writel_relaxed((uint32_t)bamdata->data_fifo.phys_base, + drvdata->base + TMC_DBALO); + writel_relaxed((((uint64_t)bamdata->data_fifo.phys_base) >> 32) & 0xFF, + drvdata->base + TMC_DBAHI); + /* Set FOnFlIn for periodic flush */ + writel_relaxed(0x133, drvdata->base + TMC_FFCR); + writel_relaxed(drvdata->trigger_cntr, drvdata->base + TMC_TRG); + tmc_enable_hw(drvdata); + + CS_LOCK(drvdata->base); + + drvdata->enable_to_bam = true; +} + +static int tmc_etr_bam_enable(struct tmc_drvdata *drvdata) +{ + struct tmc_etr_bam_data *bamdata = drvdata->bamdata; + int ret; + + if (bamdata->enable) + return 0; + + /* Reset bam to start with */ + ret = sps_device_reset(bamdata->handle); + if (ret) + goto err0; + + /* Now configure and enable bam */ + + bamdata->pipe = sps_alloc_endpoint(); + if (!bamdata->pipe) + return -ENOMEM; + + ret = sps_get_config(bamdata->pipe, &bamdata->connect); + if (ret) + goto err1; + + bamdata->connect.mode = SPS_MODE_SRC; + bamdata->connect.source = bamdata->handle; + bamdata->connect.event_thresh = 0x4; + bamdata->connect.src_pipe_index = TMC_ETR_BAM_PIPE_INDEX; + bamdata->connect.options = SPS_O_AUTO_ENABLE; + + bamdata->connect.destination = bamdata->dest; + bamdata->connect.dest_pipe_index = bamdata->dest_pipe_idx; + bamdata->connect.desc = bamdata->desc_fifo; + bamdata->connect.data = bamdata->data_fifo; + + ret = sps_connect(bamdata->pipe, &bamdata->connect); + if (ret) + goto err1; + + bamdata->enable = true; + return 0; +err1: + sps_free_endpoint(bamdata->pipe); +err0: + return ret; +} + +static void tmc_wait_for_flush(struct tmc_drvdata *drvdata) +{ + int count; + + /* Ensure no flush is in progress */ + for (count = TIMEOUT_US; + BVAL(readl_relaxed(drvdata->base + TMC_FFSR), 0) != 0 + && count > 0; count--) + udelay(1); + WARN(count == 0, "timeout while waiting for TMC flush, TMC_FFSR: %#x\n", + readl_relaxed(drvdata->base + TMC_FFSR)); +} + +static void __tmc_etr_disable_to_bam(struct tmc_drvdata *drvdata) +{ + if (!drvdata->enable_to_bam) + return; + + /* Ensure periodic flush is disabled in CSR block */ + msm_qdss_csr_disable_flush(); + + CS_UNLOCK(drvdata->base); + + tmc_wait_for_flush(drvdata); + tmc_disable_hw(drvdata); + + CS_LOCK(drvdata); + + /* Disable CSR configuration */ + msm_qdss_csr_disable_bam_to_usb(); + drvdata->enable_to_bam = false; +} + +static void tmc_etr_bam_disable(struct tmc_drvdata *drvdata) +{ + struct tmc_etr_bam_data *bamdata = drvdata->bamdata; + + if (!bamdata->enable) + return; + + sps_disconnect(bamdata->pipe); + sps_free_endpoint(bamdata->pipe); + bamdata->enable = false; +} + +static void usb_notifier(void *priv, unsigned int event, + struct qdss_request *d_req, struct usb_qdss_ch *ch) +{ + struct tmc_drvdata *drvdata = priv; + unsigned long flags; + int ret = 0; + + mutex_lock(&drvdata->mem_lock); + if (event == USB_QDSS_CONNECT) { + tmc_etr_fill_usb_bam_data(drvdata); + ret = tmc_etr_bam_enable(drvdata); + if (ret) + dev_err(drvdata->dev, "ETR BAM enable failed\n"); + + spin_lock_irqsave(&drvdata->spinlock, flags); + __tmc_etr_enable_to_bam(drvdata); + spin_unlock_irqrestore(&drvdata->spinlock, flags); + } else if (event == USB_QDSS_DISCONNECT) { + spin_lock_irqsave(&drvdata->spinlock, flags); + __tmc_etr_disable_to_bam(drvdata); + spin_unlock_irqrestore(&drvdata->spinlock, flags); + tmc_etr_bam_disable(drvdata); + } + mutex_unlock(&drvdata->mem_lock); +} + static int tmc_enable(struct tmc_drvdata *drvdata, enum tmc_mode mode) { int ret; @@ -539,7 +744,8 @@ static int tmc_enable(struct tmc_drvdata *drvdata, enum tmc_mode mode) pm_runtime_get_sync(drvdata->dev); mutex_lock(&drvdata->mem_lock); - if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { + if (drvdata->config_type == TMC_CONFIG_TYPE_ETR && + drvdata->out_mode == TMC_ETR_OUT_MODE_MEM) { /* * ETR DDR memory is not allocated until user enables * tmc at least once. If user specifies different ETR @@ -560,20 +766,37 @@ static int tmc_enable(struct tmc_drvdata *drvdata, enum tmc_mode mode) mutex_unlock(&drvdata->mem_lock); return ret; } + } else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR && + drvdata->out_mode == TMC_ETR_OUT_MODE_USB) { + drvdata->usbch = usb_qdss_open("qdss", drvdata, + usb_notifier); + if (IS_ERR(drvdata->usbch)) { + dev_err(drvdata->dev, "usb_qdss_open failed\n"); + ret = PTR_ERR(drvdata->usbch); + pm_runtime_put(drvdata->dev); + mutex_unlock(&drvdata->mem_lock); + return ret; + } } mutex_unlock(&drvdata->mem_lock); spin_lock_irqsave(&drvdata->spinlock, flags); if (drvdata->reading) { spin_unlock_irqrestore(&drvdata->spinlock, flags); + + if (drvdata->config_type == TMC_CONFIG_TYPE_ETR + && drvdata->out_mode == TMC_ETR_OUT_MODE_USB) + usb_qdss_close(drvdata->usbch); pm_runtime_put(drvdata->dev); + return -EBUSY; } if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) { tmc_etb_enable_hw(drvdata); } else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { - tmc_etr_enable_hw(drvdata); + if (drvdata->out_mode == TMC_ETR_OUT_MODE_MEM) + tmc_etr_enable_hw(drvdata); } else { if (mode == TMC_MODE_CIRCULAR_BUFFER) tmc_etb_enable_hw(drvdata); @@ -756,7 +979,10 @@ static void tmc_disable(struct tmc_drvdata *drvdata, enum tmc_mode mode) if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) { tmc_etb_disable_hw(drvdata); } else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { - tmc_etr_disable_hw(drvdata); + if (drvdata->out_mode == TMC_ETR_OUT_MODE_USB) + __tmc_etr_disable_to_bam(drvdata); + else + tmc_etr_disable_hw(drvdata); } else { if (mode == TMC_MODE_CIRCULAR_BUFFER) tmc_etb_disable_hw(drvdata); @@ -767,6 +993,12 @@ out: drvdata->enable = false; spin_unlock_irqrestore(&drvdata->spinlock, flags); + if (drvdata->config_type == TMC_CONFIG_TYPE_ETR + && drvdata->out_mode == TMC_ETR_OUT_MODE_USB) { + tmc_etr_bam_disable(drvdata); + usb_qdss_close(drvdata->usbch); + } + pm_runtime_put(drvdata->dev); dev_info(drvdata->dev, "TMC disabled\n"); @@ -1066,6 +1298,47 @@ out: return 0; } +static int tmc_etr_bam_init(struct amba_device *adev, + struct tmc_drvdata *drvdata) +{ + int ret; + struct device *dev = &adev->dev; + struct resource res; + struct tmc_etr_bam_data *bamdata; + + bamdata = devm_kzalloc(dev, sizeof(*bamdata), GFP_KERNEL); + if (!bamdata) + return -ENOMEM; + drvdata->bamdata = bamdata; + + ret = of_address_to_resource(adev->dev.of_node, 1, &res); + if (ret) + return -ENODEV; + + bamdata->props.phys_addr = res.start; + bamdata->props.virt_addr = devm_ioremap(dev, res.start, + resource_size(&res)); + if (!bamdata->props.virt_addr) + return -ENOMEM; + bamdata->props.virt_size = resource_size(&res); + + bamdata->props.event_threshold = 0x4; /* Pipe event threshold */ + bamdata->props.summing_threshold = 0x10; /* BAM event threshold */ + bamdata->props.irq = 0; + bamdata->props.num_pipes = TMC_ETR_BAM_NR_PIPES; + + return sps_register_bam_device(&bamdata->props, &bamdata->handle); +} + +static void tmc_etr_bam_exit(struct tmc_drvdata *drvdata) +{ + struct tmc_etr_bam_data *bamdata = drvdata->bamdata; + + if (!bamdata->handle) + return; + sps_deregister_bam_device(bamdata->handle); +} + static const struct file_operations tmc_fops = { .owner = THIS_MODULE, .open = tmc_open, @@ -1215,6 +1488,99 @@ static ssize_t mem_type_store(struct device *dev, } static DEVICE_ATTR_RW(mem_type); +static ssize_t out_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent); + + return scnprintf(buf, PAGE_SIZE, "%s\n", + str_tmc_etr_out_mode[drvdata->out_mode]); +} + +static ssize_t out_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent); + char str[10] = ""; + unsigned long flags; + int ret; + + if (strlen(buf) >= 10) + return -EINVAL; + if (sscanf(buf, "%s", str) != 1) + return -EINVAL; + + mutex_lock(&drvdata->mem_lock); + if (!strcmp(str, str_tmc_etr_out_mode[TMC_ETR_OUT_MODE_MEM])) { + if (drvdata->out_mode == TMC_ETR_OUT_MODE_MEM) + goto out; + + spin_lock_irqsave(&drvdata->spinlock, flags); + if (!drvdata->enable) { + drvdata->out_mode = TMC_ETR_OUT_MODE_MEM; + spin_unlock_irqrestore(&drvdata->spinlock, flags); + goto out; + } + __tmc_etr_disable_to_bam(drvdata); + tmc_etr_enable_hw(drvdata); + drvdata->out_mode = TMC_ETR_OUT_MODE_MEM; + spin_unlock_irqrestore(&drvdata->spinlock, flags); + tmc_etr_bam_disable(drvdata); + usb_qdss_close(drvdata->usbch); + } else if (!strcmp(str, str_tmc_etr_out_mode[TMC_ETR_OUT_MODE_USB])) { + if (drvdata->out_mode == TMC_ETR_OUT_MODE_USB) + goto out; + + spin_lock_irqsave(&drvdata->spinlock, flags); + if (!drvdata->enable) { + drvdata->out_mode = TMC_ETR_OUT_MODE_USB; + spin_unlock_irqrestore(&drvdata->spinlock, flags); + goto out; + } + if (drvdata->reading) { + ret = -EBUSY; + goto err1; + } + tmc_etr_disable_hw(drvdata); + drvdata->out_mode = TMC_ETR_OUT_MODE_USB; + spin_unlock_irqrestore(&drvdata->spinlock, flags); + + drvdata->usbch = usb_qdss_open("qdss", drvdata, + usb_notifier); + if (IS_ERR(drvdata->usbch)) { + dev_err(drvdata->dev, "usb_qdss_open failed\n"); + ret = PTR_ERR(drvdata->usbch); + goto err0; + } + } +out: + mutex_unlock(&drvdata->mem_lock); + return size; +err1: + spin_unlock_irqrestore(&drvdata->spinlock, flags); +err0: + mutex_unlock(&drvdata->mem_lock); + return ret; +} +static DEVICE_ATTR_RW(out_mode); + +static ssize_t available_out_modes_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(str_tmc_etr_out_mode); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%s ", + str_tmc_etr_out_mode[i]); + + len += scnprintf(buf + len, PAGE_SIZE - len, "\n"); + return len; +} +static DEVICE_ATTR_RO(available_out_modes); + static struct attribute *coresight_etb_attrs[] = { &dev_attr_trigger_cntr.attr, &dev_attr_status.attr, @@ -1223,8 +1589,10 @@ static struct attribute *coresight_etb_attrs[] = { ATTRIBUTE_GROUPS(coresight_etb); static struct attribute *coresight_etr_attrs[] = { + &dev_attr_available_out_modes.attr, &dev_attr_mem_size.attr, &dev_attr_mem_type.attr, + &dev_attr_out_mode.attr, &dev_attr_trigger_cntr.attr, &dev_attr_status.attr, NULL, @@ -1278,6 +1646,7 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id) drvdata->config_type = BMVAL(devid, 6, 7); if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { + drvdata->out_mode = TMC_ETR_OUT_MODE_MEM; if (np) ret = of_property_read_u32(np, "arm,buffer-size", @@ -1294,7 +1663,11 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id) pm_runtime_put(&adev->dev); - if (drvdata->config_type != TMC_CONFIG_TYPE_ETR) { + if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { + ret = tmc_etr_bam_init(adev, drvdata); + if (ret) + return ret; + } else { drvdata->buf = devm_kzalloc(dev, drvdata->size, GFP_KERNEL); if (!drvdata->buf) return -ENOMEM; @@ -1350,6 +1723,7 @@ static int tmc_remove(struct amba_device *adev) coresight_unregister(drvdata->csdev); if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) tmc_etr_free_mem(drvdata); + tmc_etr_bam_exit(drvdata); return 0; } |