| /* Copyright (c) 2002,2007-2017, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| #include <linux/module.h> |
| #include <linux/uaccess.h> |
| |
| #include "kgsl.h" |
| #include "adreno.h" |
| #include "adreno_perfcounter.h" |
| #include "adreno_pm4types.h" |
| #include "a5xx_reg.h" |
| |
| /* Bit flag for RBMM_PERFCTR_CTL */ |
| #define RBBM_PERFCTR_CTL_ENABLE 0x00000001 |
| |
| #define VBIF2_PERF_CNT_SEL_MASK 0x7F |
| /* offset of clear register from select register */ |
| #define VBIF2_PERF_CLR_REG_SEL_OFF 8 |
| /* offset of enable register from select register */ |
| #define VBIF2_PERF_EN_REG_SEL_OFF 16 |
| |
| /* offset of clear register from the enable register */ |
| #define VBIF2_PERF_PWR_CLR_REG_EN_OFF 8 |
| |
| #define REG_64BIT_VAL(hi, lo, val) (((((uint64_t) hi) << 32) | lo) + val) |
| /* |
| * Return true if the countable is used and not broken |
| */ |
| static inline int active_countable(unsigned int countable) |
| { |
| return ((countable != KGSL_PERFCOUNTER_NOT_USED) && |
| (countable != KGSL_PERFCOUNTER_BROKEN)); |
| } |
| |
| /** |
| * adreno_perfcounter_init: Reserve kernel performance counters |
| * @adreno_dev: Pointer to an adreno_device struct |
| * |
| * The kernel needs/wants a certain group of performance counters for |
| * its own activities. Reserve these performance counters at init time |
| * to ensure that they are always reserved for the kernel. The performance |
| * counters used by the kernel can be obtained by the user, but these |
| * performance counters will remain active as long as the device is alive. |
| */ |
| void adreno_perfcounter_init(struct adreno_device *adreno_dev) |
| { |
| struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev); |
| |
| if (gpudev->perfcounter_init) |
| gpudev->perfcounter_init(adreno_dev); |
| } |
| |
| /** |
| * adreno_perfcounter_write() - Write the physical performance |
| * counter values. |
| * @adreno_dev - Adreno device whose registers are to be written to. |
| * @reg - register address of the physical counter to which the value is |
| * written to. |
| * |
| * This function loads the 64 bit saved value into the particular physical |
| * counter by enabling the corresponding bit in A3XX_RBBM_PERFCTR_LOAD_CMD* |
| * register. |
| */ |
| static void adreno_perfcounter_write(struct adreno_device *adreno_dev, |
| struct adreno_perfcount_register *reg) |
| { |
| unsigned int val, i; |
| int cmd[] = { ADRENO_REG_RBBM_PERFCTR_LOAD_CMD0, |
| ADRENO_REG_RBBM_PERFCTR_LOAD_CMD1, |
| ADRENO_REG_RBBM_PERFCTR_LOAD_CMD2, |
| ADRENO_REG_RBBM_PERFCTR_LOAD_CMD3 }; |
| |
| /* If not loadable then return quickly */ |
| if (reg->load_bit < 0) |
| return; |
| |
| /* Get the offset/cmd for loading */ |
| i = reg->load_bit / 32; |
| |
| /* Get the register bit offset for loading */ |
| val = BIT(reg->load_bit & 31); |
| |
| /* Write the saved value to PERFCTR_LOAD_VALUE* registers. */ |
| adreno_writereg64(adreno_dev, ADRENO_REG_RBBM_PERFCTR_LOAD_VALUE_LO, |
| ADRENO_REG_RBBM_PERFCTR_LOAD_VALUE_HI, reg->value); |
| |
| /* |
| * Set the load bit in PERFCTR_LOAD_CMD for the physical counter |
| * we want to restore. The value in PERFCTR_LOAD_VALUE* is loaded |
| * into the corresponding physical counter. The value for the select |
| * register gets cleared once RBBM reads it so no need to clear the |
| * select register afterwards. |
| */ |
| adreno_writereg(adreno_dev, cmd[i], val); |
| } |
| |
| /** |
| * adreno_perfcounter_close() - Release counters initialized by |
| * adreno_perfcounter_close |
| * @adreno_dev: Pointer to an adreno_device struct |
| */ |
| void adreno_perfcounter_close(struct adreno_device *adreno_dev) |
| { |
| struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev); |
| |
| if (gpudev->perfcounter_close) |
| gpudev->perfcounter_close(adreno_dev); |
| } |
| |
| /** |
| * adreno_perfcounter_restore() - Restore performance counters |
| * @adreno_dev: adreno device to configure |
| * |
| * Load the physical performance counters with 64 bit value which are |
| * saved on GPU power collapse. |
| */ |
| void adreno_perfcounter_restore(struct adreno_device *adreno_dev) |
| { |
| struct adreno_perfcounters *counters = ADRENO_PERFCOUNTERS(adreno_dev); |
| struct adreno_perfcount_group *group; |
| unsigned int counter, groupid; |
| |
| if (counters == NULL) |
| return; |
| |
| for (groupid = 0; groupid < counters->group_count; groupid++) { |
| group = &(counters->groups[groupid]); |
| |
| /* Restore the counters for the group */ |
| for (counter = 0; counter < group->reg_count; counter++) { |
| /* If not active or broken, skip this counter */ |
| if (!active_countable(group->regs[counter].countable)) |
| continue; |
| |
| adreno_perfcounter_write(adreno_dev, |
| &group->regs[counter]); |
| } |
| } |
| } |
| |
| /** |
| * adreno_perfcounter_save() - Save performance counters |
| * @adreno_dev: adreno device to configure |
| * |
| * Save the performance counter values before GPU power collapse. |
| * The saved values are restored on restart. |
| * This ensures physical counters are coherent across power-collapse. |
| */ |
| inline void adreno_perfcounter_save(struct adreno_device *adreno_dev) |
| { |
| struct adreno_perfcounters *counters = ADRENO_PERFCOUNTERS(adreno_dev); |
| struct adreno_perfcount_group *group; |
| unsigned int counter, groupid; |
| |
| if (counters == NULL) |
| return; |
| |
| for (groupid = 0; groupid < counters->group_count; groupid++) { |
| group = &(counters->groups[groupid]); |
| |
| /* Save the counter values for the group */ |
| for (counter = 0; counter < group->reg_count; counter++) { |
| /* If not active or broken, skip this counter */ |
| if (!active_countable(group->regs[counter].countable)) |
| continue; |
| |
| /* accumulate values for non-loadable counters */ |
| if (group->regs[counter].load_bit >= 0) |
| group->regs[counter].value = 0; |
| |
| group->regs[counter].value = |
| group->regs[counter].value + |
| adreno_perfcounter_read(adreno_dev, groupid, |
| counter); |
| } |
| } |
| } |
| |
| static int adreno_perfcounter_enable(struct adreno_device *adreno_dev, |
| unsigned int group, unsigned int counter, unsigned int countable); |
| |
| /** |
| * adreno_perfcounter_start: Enable performance counters |
| * @adreno_dev: Adreno device to configure |
| * |
| * Ensure all performance counters are enabled that are allocated. Since |
| * the device was most likely stopped, we can't trust that the counters |
| * are still valid so make it so. |
| */ |
| |
| void adreno_perfcounter_start(struct adreno_device *adreno_dev) |
| { |
| struct adreno_perfcounters *counters = ADRENO_PERFCOUNTERS(adreno_dev); |
| struct adreno_perfcount_group *group; |
| unsigned int i, j; |
| |
| if (counters == NULL) |
| return; |
| /* group id iter */ |
| for (i = 0; i < counters->group_count; i++) { |
| group = &(counters->groups[i]); |
| |
| /* countable iter */ |
| for (j = 0; j < group->reg_count; j++) { |
| if (!active_countable(group->regs[j].countable)) |
| continue; |
| |
| /* |
| * The GPU has to be idle before calling the perfcounter |
| * enable function, but since this function is called |
| * during start we already know the GPU is idle. |
| * Since the countable/counter pairs have already been |
| * validated, there is no way for _enable() to fail so |
| * no need to check the return code. |
| */ |
| adreno_perfcounter_enable(adreno_dev, i, j, |
| group->regs[j].countable); |
| } |
| } |
| } |
| |
| /** |
| * adreno_perfcounter_read_group() - Determine which countables are in counters |
| * @adreno_dev: Adreno device to configure |
| * @reads: List of kgsl_perfcounter_read_groups |
| * @count: Length of list |
| * |
| * Read the performance counters for the groupid/countable pairs and return |
| * the 64 bit result for each pair |
| */ |
| |
| int adreno_perfcounter_read_group(struct adreno_device *adreno_dev, |
| struct kgsl_perfcounter_read_group __user *reads, unsigned int count) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| struct adreno_perfcounters *counters = ADRENO_PERFCOUNTERS(adreno_dev); |
| struct adreno_perfcount_group *group; |
| struct kgsl_perfcounter_read_group *list = NULL; |
| unsigned int i, j; |
| int ret = 0; |
| |
| if (counters == NULL) |
| return -EINVAL; |
| |
| /* sanity check params passed in */ |
| if (reads == NULL || count == 0 || count > 100) |
| return -EINVAL; |
| |
| list = kmalloc_array(count, sizeof(struct kgsl_perfcounter_read_group), |
| GFP_KERNEL); |
| if (!list) |
| return -ENOMEM; |
| |
| if (copy_from_user(list, reads, |
| sizeof(struct kgsl_perfcounter_read_group) * count)) { |
| ret = -EFAULT; |
| goto done; |
| } |
| |
| mutex_lock(&device->mutex); |
| |
| ret = adreno_perfcntr_active_oob_get(adreno_dev); |
| if (ret) { |
| mutex_unlock(&device->mutex); |
| goto done; |
| } |
| |
| /* list iterator */ |
| for (j = 0; j < count; j++) { |
| |
| list[j].value = 0; |
| |
| /* Verify that the group ID is within range */ |
| if (list[j].groupid >= counters->group_count) { |
| ret = -EINVAL; |
| break; |
| } |
| |
| group = &(counters->groups[list[j].groupid]); |
| |
| /* group/counter iterator */ |
| for (i = 0; i < group->reg_count; i++) { |
| if (group->regs[i].countable == list[j].countable) { |
| list[j].value = adreno_perfcounter_read( |
| adreno_dev, list[j].groupid, i); |
| break; |
| } |
| } |
| } |
| |
| adreno_perfcntr_active_oob_put(adreno_dev); |
| |
| mutex_unlock(&device->mutex); |
| |
| /* write the data */ |
| if (ret == 0) |
| if (copy_to_user(reads, list, |
| sizeof(struct kgsl_perfcounter_read_group) * count)) |
| ret = -EFAULT; |
| |
| done: |
| kfree(list); |
| return ret; |
| } |
| |
| /** |
| * adreno_perfcounter_get_groupid() - Get the performance counter ID |
| * @adreno_dev: Adreno device |
| * @name: Performance counter group name string |
| * |
| * Get the groupid based on the name and return this ID |
| */ |
| |
| int adreno_perfcounter_get_groupid(struct adreno_device *adreno_dev, |
| const char *name) |
| { |
| struct adreno_perfcounters *counters = ADRENO_PERFCOUNTERS(adreno_dev); |
| struct adreno_perfcount_group *group; |
| int i; |
| |
| if (name == NULL || counters == NULL) |
| return -EINVAL; |
| |
| for (i = 0; i < counters->group_count; ++i) { |
| group = &(counters->groups[i]); |
| |
| /* make sure there is a name for this group */ |
| if (group->name == NULL) |
| continue; |
| |
| /* verify name and length */ |
| if (strlen(name) == strlen(group->name) && |
| strcmp(group->name, name) == 0) |
| return i; |
| } |
| |
| return -EINVAL; |
| } |
| |
| /** |
| * adreno_perfcounter_get_name() - Get the group name |
| * @adreno_dev: Adreno device |
| * @groupid: Desired performance counter groupid |
| * |
| * Get the name based on the groupid and return it |
| */ |
| |
| const char *adreno_perfcounter_get_name(struct adreno_device *adreno_dev, |
| unsigned int groupid) |
| { |
| struct adreno_perfcounters *counters = ADRENO_PERFCOUNTERS(adreno_dev); |
| |
| if (counters != NULL && groupid < counters->group_count) |
| return counters->groups[groupid].name; |
| |
| return NULL; |
| } |
| |
| /** |
| * adreno_perfcounter_query_group: Determine which countables are in counters |
| * @adreno_dev: Adreno device to configure |
| * @groupid: Desired performance counter group |
| * @countables: Return list of all countables in the groups counters |
| * @count: Max length of the array |
| * @max_counters: max counters for the groupid |
| * |
| * Query the current state of counters for the group. |
| */ |
| |
| int adreno_perfcounter_query_group(struct adreno_device *adreno_dev, |
| unsigned int groupid, unsigned int __user *countables, |
| unsigned int count, unsigned int *max_counters) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| struct adreno_perfcounters *counters = ADRENO_PERFCOUNTERS(adreno_dev); |
| struct adreno_perfcount_group *group; |
| unsigned int i, t; |
| int ret = 0; |
| unsigned int *buf; |
| |
| *max_counters = 0; |
| |
| if (counters == NULL || groupid >= counters->group_count) |
| return -EINVAL; |
| |
| mutex_lock(&device->mutex); |
| |
| group = &(counters->groups[groupid]); |
| *max_counters = group->reg_count; |
| |
| /* |
| * if NULL countable or *count of zero, return max reg_count in |
| * *max_counters and return success |
| */ |
| if (countables == NULL || count == 0) { |
| mutex_unlock(&device->mutex); |
| return 0; |
| } |
| |
| t = min_t(unsigned int, group->reg_count, count); |
| |
| buf = kmalloc_array(t, sizeof(unsigned int), GFP_KERNEL); |
| if (buf == NULL) { |
| mutex_unlock(&device->mutex); |
| return -ENOMEM; |
| } |
| |
| for (i = 0; i < t; i++) |
| buf[i] = group->regs[i].countable; |
| |
| mutex_unlock(&device->mutex); |
| |
| if (copy_to_user(countables, buf, sizeof(unsigned int) * t)) |
| ret = -EFAULT; |
| |
| kfree(buf); |
| |
| return ret; |
| } |
| |
| static inline void refcount_group(struct adreno_perfcount_group *group, |
| unsigned int reg, unsigned int flags, |
| unsigned int *lo, unsigned int *hi) |
| { |
| if (flags & PERFCOUNTER_FLAG_KERNEL) |
| group->regs[reg].kernelcount++; |
| else |
| group->regs[reg].usercount++; |
| |
| if (lo) |
| *lo = group->regs[reg].offset; |
| |
| if (hi) |
| *hi = group->regs[reg].offset_hi; |
| } |
| |
| /** |
| * adreno_perfcounter_get: Try to put a countable in an available counter |
| * @adreno_dev: Adreno device to configure |
| * @groupid: Desired performance counter group |
| * @countable: Countable desired to be in a counter |
| * @offset: Return offset of the LO counter assigned |
| * @offset_hi: Return offset of the HI counter assigned |
| * @flags: Used to setup kernel perf counters |
| * |
| * Try to place a countable in an available counter. If the countable is |
| * already in a counter, reference count the counter/countable pair resource |
| * and return success |
| */ |
| |
| int adreno_perfcounter_get(struct adreno_device *adreno_dev, |
| unsigned int groupid, unsigned int countable, unsigned int *offset, |
| unsigned int *offset_hi, unsigned int flags) |
| { |
| struct adreno_perfcounters *counters = ADRENO_PERFCOUNTERS(adreno_dev); |
| struct adreno_perfcount_group *group; |
| unsigned int empty = -1; |
| int ret = 0; |
| |
| /* always clear return variables */ |
| if (offset) |
| *offset = 0; |
| if (offset_hi) |
| *offset_hi = 0; |
| |
| if (counters == NULL) |
| return -EINVAL; |
| |
| if (groupid >= counters->group_count) |
| return -EINVAL; |
| |
| group = &(counters->groups[groupid]); |
| |
| if (group->flags & ADRENO_PERFCOUNTER_GROUP_FIXED) { |
| /* |
| * In fixed groups the countable equals the fixed register the |
| * user wants. First make sure it is in range |
| */ |
| |
| if (countable >= group->reg_count) |
| return -EINVAL; |
| |
| /* If it is already reserved, just increase the refcounts */ |
| if ((group->regs[countable].kernelcount != 0) || |
| (group->regs[countable].usercount != 0)) { |
| refcount_group(group, countable, flags, |
| offset, offset_hi); |
| return 0; |
| } |
| |
| empty = countable; |
| } else { |
| unsigned int i; |
| |
| /* |
| * Check if the countable is already associated with a counter. |
| * Refcount and return the offset, otherwise, try and find an |
| * empty counter and assign the countable to it. |
| */ |
| |
| for (i = 0; i < group->reg_count; i++) { |
| if (group->regs[i].countable == countable) { |
| refcount_group(group, i, flags, |
| offset, offset_hi); |
| return 0; |
| } else if (group->regs[i].countable == |
| KGSL_PERFCOUNTER_NOT_USED) { |
| /* keep track of unused counter */ |
| empty = i; |
| } |
| } |
| } |
| |
| /* no available counters, so do nothing else */ |
| if (empty == -1) |
| return -EBUSY; |
| |
| /* initialize the new counter */ |
| group->regs[empty].countable = countable; |
| |
| /* enable the new counter */ |
| ret = adreno_perfcounter_enable(adreno_dev, groupid, empty, countable); |
| if (ret) { |
| /* Put back the perfcounter */ |
| if (!(group->flags & ADRENO_PERFCOUNTER_GROUP_FIXED)) |
| group->regs[empty].countable = |
| KGSL_PERFCOUNTER_NOT_USED; |
| return ret; |
| } |
| |
| /* set initial kernel and user count */ |
| if (flags & PERFCOUNTER_FLAG_KERNEL) { |
| group->regs[empty].kernelcount = 1; |
| group->regs[empty].usercount = 0; |
| } else { |
| group->regs[empty].kernelcount = 0; |
| group->regs[empty].usercount = 1; |
| } |
| |
| if (offset) |
| *offset = group->regs[empty].offset; |
| if (offset_hi) |
| *offset_hi = group->regs[empty].offset_hi; |
| |
| return ret; |
| } |
| |
| |
| /** |
| * adreno_perfcounter_put: Release a countable from counter resource |
| * @adreno_dev: Adreno device to configure |
| * @groupid: Desired performance counter group |
| * @countable: Countable desired to be freed from a counter |
| * @flags: Flag to determine if kernel or user space request |
| * |
| * Put a performance counter/countable pair that was previously received. If |
| * noone else is using the countable, free up the counter for others. |
| */ |
| int adreno_perfcounter_put(struct adreno_device *adreno_dev, |
| unsigned int groupid, unsigned int countable, unsigned int flags) |
| { |
| struct adreno_perfcounters *counters = ADRENO_PERFCOUNTERS(adreno_dev); |
| struct adreno_perfcount_group *group; |
| unsigned int i; |
| |
| if (counters == NULL || groupid >= counters->group_count) |
| return -EINVAL; |
| |
| group = &(counters->groups[groupid]); |
| |
| /* |
| * Find if the counter/countable pair is used currently. |
| * Start cycling through registers in the bank. |
| */ |
| for (i = 0; i < group->reg_count; i++) { |
| /* check if countable assigned is what we are looking for */ |
| if (group->regs[i].countable == countable) { |
| /* found pair, book keep count based on request type */ |
| if (flags & PERFCOUNTER_FLAG_KERNEL && |
| group->regs[i].kernelcount > 0) |
| group->regs[i].kernelcount--; |
| else if (group->regs[i].usercount > 0) |
| group->regs[i].usercount--; |
| else |
| break; |
| |
| /* mark available if not used anymore */ |
| if (group->regs[i].kernelcount == 0 && |
| group->regs[i].usercount == 0) |
| group->regs[i].countable = |
| KGSL_PERFCOUNTER_NOT_USED; |
| |
| return 0; |
| } |
| } |
| |
| return -EINVAL; |
| } |
| |
| static void _perfcounter_enable_vbif(struct adreno_device *adreno_dev, |
| struct adreno_perfcounters *counters, unsigned int counter, |
| unsigned int countable) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| struct adreno_perfcount_register *reg; |
| |
| reg = &counters->groups[KGSL_PERFCOUNTER_GROUP_VBIF].regs[counter]; |
| /* Write 1, followed by 0 to CLR register for clearing the counter */ |
| kgsl_regwrite(device, reg->select - VBIF2_PERF_CLR_REG_SEL_OFF, 1); |
| kgsl_regwrite(device, reg->select - VBIF2_PERF_CLR_REG_SEL_OFF, 0); |
| kgsl_regwrite(device, reg->select, countable & VBIF2_PERF_CNT_SEL_MASK); |
| /* enable reg is 8 DWORDS before select reg */ |
| kgsl_regwrite(device, reg->select - VBIF2_PERF_EN_REG_SEL_OFF, 1); |
| reg->value = 0; |
| } |
| |
| static void _perfcounter_enable_vbif_pwr(struct adreno_device *adreno_dev, |
| struct adreno_perfcounters *counters, unsigned int counter) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| struct adreno_perfcount_register *reg; |
| |
| reg = &counters->groups[KGSL_PERFCOUNTER_GROUP_VBIF_PWR].regs[counter]; |
| /* Write 1, followed by 0 to CLR register for clearing the counter */ |
| kgsl_regwrite(device, reg->select + VBIF2_PERF_PWR_CLR_REG_EN_OFF, 1); |
| kgsl_regwrite(device, reg->select + VBIF2_PERF_PWR_CLR_REG_EN_OFF, 0); |
| kgsl_regwrite(device, reg->select, 1); |
| reg->value = 0; |
| } |
| |
| static void _power_counter_enable_alwayson(struct adreno_device *adreno_dev, |
| struct adreno_perfcounters *counters) |
| { |
| if (!ADRENO_FEATURE(adreno_dev, ADRENO_GPMU)) |
| return; |
| |
| kgsl_regwrite(KGSL_DEVICE(adreno_dev), |
| A5XX_GPMU_ALWAYS_ON_COUNTER_RESET, 1); |
| counters->groups[KGSL_PERFCOUNTER_GROUP_ALWAYSON_PWR].regs[0].value = 0; |
| } |
| |
| static void _power_counter_enable_gpmu(struct adreno_device *adreno_dev, |
| struct adreno_perfcounters *counters, unsigned int group, |
| unsigned int counter, unsigned int countable) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| struct adreno_perfcount_register *reg; |
| unsigned int shift = (counter << 3) % (sizeof(unsigned int) * 8); |
| |
| if (adreno_is_a530(adreno_dev)) { |
| if (countable > 43) |
| return; |
| } else if (adreno_is_a540(adreno_dev)) { |
| if (countable > 47) |
| return; |
| } else if (adreno_is_a6xx(adreno_dev)) { |
| if (countable > 34) |
| return; |
| } else |
| /* return on platforms that have no GPMU */ |
| return; |
| |
| reg = &counters->groups[group].regs[counter]; |
| kgsl_regrmw(device, reg->select, 0xff << shift, countable << shift); |
| adreno_writereg(adreno_dev, ADRENO_REG_GPMU_POWER_COUNTER_ENABLE, 1); |
| reg->value = 0; |
| } |
| |
| static void _power_counter_enable_default(struct adreno_device *adreno_dev, |
| struct adreno_perfcounters *counters, unsigned int group, |
| unsigned int counter, unsigned int countable) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| struct adreno_perfcount_register *reg; |
| |
| if (!ADRENO_FEATURE(adreno_dev, ADRENO_GPMU)) |
| return; |
| |
| reg = &counters->groups[group].regs[counter]; |
| kgsl_regwrite(device, reg->select, countable); |
| adreno_writereg(adreno_dev, ADRENO_REG_GPMU_POWER_COUNTER_ENABLE, 1); |
| reg->value = 0; |
| } |
| |
| static int _perfcounter_enable_default(struct adreno_device *adreno_dev, |
| struct adreno_perfcounters *counters, unsigned int group, |
| unsigned int counter, unsigned int countable) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev); |
| struct adreno_perfcount_register *reg; |
| int i; |
| int ret = 0; |
| |
| /* |
| * check whether the countable is valid or not by matching it against |
| * the list on invalid countables |
| */ |
| if (gpudev->invalid_countables) { |
| struct adreno_invalid_countables invalid_countable = |
| gpudev->invalid_countables[group]; |
| for (i = 0; i < invalid_countable.num_countables; i++) |
| if (countable == invalid_countable.countables[i]) |
| return -EACCES; |
| } |
| reg = &(counters->groups[group].regs[counter]); |
| |
| if (!adreno_is_a6xx(adreno_dev) && |
| test_bit(ADRENO_DEVICE_STARTED, &adreno_dev->priv)) { |
| struct adreno_ringbuffer *rb = &adreno_dev->ringbuffers[0]; |
| unsigned int buf[4]; |
| unsigned int *cmds = buf; |
| int ret; |
| |
| cmds += cp_wait_for_idle(adreno_dev, cmds); |
| *cmds++ = cp_register(adreno_dev, reg->select, 1); |
| *cmds++ = countable; |
| /* submit to highest priority RB always */ |
| ret = adreno_ringbuffer_issuecmds(rb, KGSL_CMD_FLAGS_PMODE, |
| buf, cmds-buf); |
| if (ret) |
| return ret; |
| /* |
| * schedule dispatcher to make sure rb[0] is run, because |
| * if the current RB is not rb[0] and gpu is idle then |
| * rb[0] will not get scheduled to run |
| */ |
| if (adreno_dev->cur_rb != rb) |
| adreno_dispatcher_schedule(device); |
| /* wait for the above commands submitted to complete */ |
| ret = adreno_ringbuffer_waittimestamp(rb, rb->timestamp, |
| ADRENO_IDLE_TIMEOUT); |
| if (ret) { |
| /* |
| * If we were woken up because of cancelling rb events |
| * either due to soft reset or adreno_stop, ignore the |
| * error and return 0 here. The perfcounter is already |
| * set up in software and it will be programmed in |
| * hardware when we wake up or come up after soft reset, |
| * by adreno_perfcounter_restore. |
| */ |
| if (ret == -EAGAIN) |
| ret = 0; |
| else |
| KGSL_DRV_ERR(device, |
| "Perfcounter %u/%u/%u start via commands failed %d\n", |
| group, counter, countable, ret); |
| } |
| } else { |
| /* Select the desired perfcounter */ |
| kgsl_regwrite(device, reg->select, countable); |
| } |
| |
| if (!ret) |
| reg->value = 0; |
| return 0; |
| } |
| |
| /** |
| * adreno_perfcounter_enable - Configure a performance counter for a countable |
| * @adreno_dev - Adreno device to configure |
| * @group - Desired performance counter group |
| * @counter - Desired performance counter in the group |
| * @countable - Desired countable |
| * |
| * Function is used for adreno cores |
| * Physically set up a counter within a group with the desired countable |
| * Return 0 on success else error code |
| */ |
| static int adreno_perfcounter_enable(struct adreno_device *adreno_dev, |
| unsigned int group, unsigned int counter, unsigned int countable) |
| { |
| struct adreno_perfcounters *counters = ADRENO_PERFCOUNTERS(adreno_dev); |
| struct adreno_gpudev *gpudev = ADRENO_GPU_DEVICE(adreno_dev); |
| |
| if (counters == NULL) |
| return -EINVAL; |
| |
| if (group >= counters->group_count) |
| return -EINVAL; |
| |
| if (counter >= counters->groups[group].reg_count) |
| return -EINVAL; |
| |
| switch (group) { |
| case KGSL_PERFCOUNTER_GROUP_ALWAYSON: |
| /* alwayson counter is global, so init value is 0 */ |
| break; |
| case KGSL_PERFCOUNTER_GROUP_PWR: |
| if (gpudev->enable_pwr_counters) |
| return gpudev->enable_pwr_counters(adreno_dev, counter); |
| return 0; |
| case KGSL_PERFCOUNTER_GROUP_VBIF: |
| if (countable > VBIF2_PERF_CNT_SEL_MASK) |
| return -EINVAL; |
| _perfcounter_enable_vbif(adreno_dev, counters, counter, |
| countable); |
| break; |
| case KGSL_PERFCOUNTER_GROUP_VBIF_PWR: |
| _perfcounter_enable_vbif_pwr(adreno_dev, counters, counter); |
| break; |
| case KGSL_PERFCOUNTER_GROUP_SP_PWR: |
| case KGSL_PERFCOUNTER_GROUP_TP_PWR: |
| case KGSL_PERFCOUNTER_GROUP_RB_PWR: |
| case KGSL_PERFCOUNTER_GROUP_CCU_PWR: |
| case KGSL_PERFCOUNTER_GROUP_UCHE_PWR: |
| case KGSL_PERFCOUNTER_GROUP_CP_PWR: |
| _power_counter_enable_default(adreno_dev, counters, group, |
| counter, countable); |
| break; |
| case KGSL_PERFCOUNTER_GROUP_GPMU_PWR: |
| _power_counter_enable_gpmu(adreno_dev, counters, group, counter, |
| countable); |
| break; |
| case KGSL_PERFCOUNTER_GROUP_ALWAYSON_PWR: |
| _power_counter_enable_alwayson(adreno_dev, counters); |
| break; |
| case KGSL_PERFCOUNTER_GROUP_RBBM: |
| /* The following rbbm countable is not reliable on a540 */ |
| if (adreno_is_a540(adreno_dev)) |
| if (countable == A5XX_RBBM_ALWAYS_COUNT) |
| return -EINVAL; |
| default: |
| return _perfcounter_enable_default(adreno_dev, counters, group, |
| counter, countable); |
| } |
| |
| return 0; |
| } |
| |
| static uint64_t _perfcounter_read_alwayson(struct adreno_device *adreno_dev, |
| struct adreno_perfcount_group *group, unsigned int counter) |
| { |
| uint64_t val = 0; |
| |
| adreno_readreg64(adreno_dev, ADRENO_REG_RBBM_ALWAYSON_COUNTER_LO, |
| ADRENO_REG_RBBM_ALWAYSON_COUNTER_HI, &val); |
| |
| return val + group->regs[counter].value; |
| } |
| |
| static uint64_t _perfcounter_read_pwr(struct adreno_device *adreno_dev, |
| struct adreno_perfcount_group *group, unsigned int counter) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| struct adreno_perfcount_register *reg; |
| unsigned int in = 0, out, lo = 0, hi = 0; |
| unsigned int enable_bit; |
| |
| reg = &group->regs[counter]; |
| |
| /* Remember, counter 0 is not emulated on 5XX */ |
| if (adreno_is_a5xx(adreno_dev) && (counter == 0)) |
| return -EINVAL; |
| |
| if (adreno_is_a3xx(adreno_dev)) { |
| /* On A3XX we need to freeze the counter so we can read it */ |
| if (counter == 0) |
| enable_bit = 0x00010000; |
| else |
| enable_bit = 0x00020000; |
| |
| /* freeze counter */ |
| adreno_readreg(adreno_dev, ADRENO_REG_RBBM_RBBM_CTL, &in); |
| out = (in & ~enable_bit); |
| adreno_writereg(adreno_dev, ADRENO_REG_RBBM_RBBM_CTL, out); |
| } |
| |
| kgsl_regread(device, reg->offset, &lo); |
| kgsl_regread(device, reg->offset_hi, &hi); |
| |
| /* restore the counter control value */ |
| if (adreno_is_a3xx(adreno_dev)) |
| adreno_writereg(adreno_dev, ADRENO_REG_RBBM_RBBM_CTL, in); |
| |
| return REG_64BIT_VAL(hi, lo, reg->value); |
| } |
| |
| static uint64_t _perfcounter_read_vbif(struct adreno_device *adreno_dev, |
| struct adreno_perfcount_group *group, unsigned int counter) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| struct adreno_perfcount_register *reg; |
| unsigned int lo = 0, hi = 0; |
| |
| reg = &group->regs[counter]; |
| |
| /* freeze counter */ |
| if (adreno_is_a3xx(adreno_dev)) |
| kgsl_regwrite(device, reg->select - VBIF2_PERF_EN_REG_SEL_OFF, |
| 0); |
| |
| kgsl_regread(device, reg->offset, &lo); |
| kgsl_regread(device, reg->offset_hi, &hi); |
| |
| /* un-freeze counter */ |
| if (adreno_is_a3xx(adreno_dev)) |
| kgsl_regwrite(device, reg->select - VBIF2_PERF_EN_REG_SEL_OFF, |
| 1); |
| |
| return REG_64BIT_VAL(hi, lo, reg->value); |
| } |
| |
| static uint64_t _perfcounter_read_vbif_pwr(struct adreno_device *adreno_dev, |
| struct adreno_perfcount_group *group, unsigned int counter) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| struct adreno_perfcount_register *reg; |
| unsigned int lo = 0, hi = 0; |
| |
| reg = &group->regs[counter]; |
| |
| /* freeze counter */ |
| if (adreno_is_a3xx(adreno_dev)) |
| kgsl_regwrite(device, reg->select, 0); |
| |
| kgsl_regread(device, reg->offset, &lo); |
| kgsl_regread(device, reg->offset_hi, &hi); |
| |
| /* un-freeze counter */ |
| if (adreno_is_a3xx(adreno_dev)) |
| kgsl_regwrite(device, reg->select, 1); |
| |
| return REG_64BIT_VAL(hi, lo, reg->value); |
| } |
| |
| static uint64_t _perfcounter_read_pwrcntr(struct adreno_device *adreno_dev, |
| struct adreno_perfcount_group *group, unsigned int counter) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| struct adreno_perfcount_register *reg; |
| unsigned int lo = 0, hi = 0; |
| |
| if (!ADRENO_FEATURE(adreno_dev, ADRENO_GPMU)) |
| return 0; |
| |
| reg = &group->regs[counter]; |
| |
| kgsl_regread(device, reg->offset, &lo); |
| kgsl_regread(device, reg->offset_hi, &hi); |
| |
| return REG_64BIT_VAL(hi, lo, reg->value); |
| } |
| |
| static uint64_t _perfcounter_read_default(struct adreno_device *adreno_dev, |
| struct adreno_perfcount_group *group, unsigned int counter) |
| { |
| struct kgsl_device *device = KGSL_DEVICE(adreno_dev); |
| struct adreno_perfcount_register *reg; |
| unsigned int lo = 0, hi = 0; |
| unsigned int in = 0, out; |
| |
| reg = &group->regs[counter]; |
| |
| /* Freeze the counter */ |
| if (adreno_is_a3xx(adreno_dev)) { |
| adreno_readreg(adreno_dev, ADRENO_REG_RBBM_PERFCTR_CTL, &in); |
| out = in & ~RBBM_PERFCTR_CTL_ENABLE; |
| adreno_writereg(adreno_dev, ADRENO_REG_RBBM_PERFCTR_CTL, out); |
| } |
| |
| /* Read the values */ |
| kgsl_regread(device, reg->offset, &lo); |
| kgsl_regread(device, reg->offset_hi, &hi); |
| |
| /* Re-Enable the counter */ |
| if (adreno_is_a3xx(adreno_dev)) |
| adreno_writereg(adreno_dev, ADRENO_REG_RBBM_PERFCTR_CTL, in); |
| |
| return REG_64BIT_VAL(hi, lo, 0); |
| } |
| |
| /** |
| * adreno_perfcounter_read() - Reads a performance counter |
| * @adreno_dev: The device on which the counter is running |
| * @group: The group of the counter |
| * @counter: The counter within the group |
| * |
| * Function is used to read the counter of adreno devices |
| * Returns the 64 bit counter value on success else 0. |
| */ |
| uint64_t adreno_perfcounter_read(struct adreno_device *adreno_dev, |
| unsigned int groupid, unsigned int counter) |
| { |
| struct adreno_perfcounters *counters = ADRENO_PERFCOUNTERS(adreno_dev); |
| struct adreno_perfcount_group *group; |
| |
| /* Lets hope this doesn't fail. Now subfunctions don't need to check */ |
| if (counters == NULL) |
| return 0; |
| |
| if (groupid >= counters->group_count) |
| return 0; |
| |
| group = &counters->groups[groupid]; |
| |
| if (counter >= group->reg_count) |
| return 0; |
| |
| switch (groupid) { |
| case KGSL_PERFCOUNTER_GROUP_ALWAYSON: |
| return _perfcounter_read_alwayson(adreno_dev, group, counter); |
| case KGSL_PERFCOUNTER_GROUP_VBIF_PWR: |
| return _perfcounter_read_vbif_pwr(adreno_dev, group, counter); |
| case KGSL_PERFCOUNTER_GROUP_VBIF: |
| return _perfcounter_read_vbif(adreno_dev, group, counter); |
| case KGSL_PERFCOUNTER_GROUP_PWR: |
| return _perfcounter_read_pwr(adreno_dev, group, counter); |
| case KGSL_PERFCOUNTER_GROUP_SP_PWR: |
| case KGSL_PERFCOUNTER_GROUP_TP_PWR: |
| case KGSL_PERFCOUNTER_GROUP_RB_PWR: |
| case KGSL_PERFCOUNTER_GROUP_CCU_PWR: |
| case KGSL_PERFCOUNTER_GROUP_UCHE_PWR: |
| case KGSL_PERFCOUNTER_GROUP_CP_PWR: |
| case KGSL_PERFCOUNTER_GROUP_GPMU_PWR: |
| case KGSL_PERFCOUNTER_GROUP_ALWAYSON_PWR: |
| return _perfcounter_read_pwrcntr(adreno_dev, group, counter); |
| default: |
| return _perfcounter_read_default(adreno_dev, group, counter); |
| } |
| } |