blob: c1b9e4ce24b2e4ab8e2e2b81f3a0243946c69505 [file] [log] [blame]
/* Copyright (c) 2002,2008-2011, Code Aurora Forum. 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/delay.h>
#include <linux/debugfs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include "kgsl.h"
#include "adreno_postmortem.h"
#include "adreno.h"
#include "a2xx_reg.h"
unsigned int kgsl_cff_dump_enable;
int kgsl_pm_regs_enabled;
static struct dentry *pm_d_debugfs;
static int pm_dump_set(void *data, u64 val)
{
struct kgsl_device *device = data;
if (val) {
mutex_lock(&device->mutex);
adreno_postmortem_dump(device, 1);
mutex_unlock(&device->mutex);
}
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(pm_dump_fops,
NULL,
pm_dump_set, "%llu\n");
static int pm_regs_enabled_set(void *data, u64 val)
{
kgsl_pm_regs_enabled = val ? 1 : 0;
return 0;
}
static int pm_regs_enabled_get(void *data, u64 *val)
{
*val = kgsl_pm_regs_enabled;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(pm_regs_enabled_fops,
pm_regs_enabled_get,
pm_regs_enabled_set, "%llu\n");
static int kgsl_cff_dump_enable_set(void *data, u64 val)
{
#ifdef CONFIG_MSM_KGSL_CFF_DUMP
kgsl_cff_dump_enable = (val != 0);
return 0;
#else
return -EINVAL;
#endif
}
static int kgsl_cff_dump_enable_get(void *data, u64 *val)
{
*val = kgsl_cff_dump_enable;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(kgsl_cff_dump_enable_fops, kgsl_cff_dump_enable_get,
kgsl_cff_dump_enable_set, "%llu\n");
static int kgsl_dbgfs_open(struct inode *inode, struct file *file)
{
file->f_mode &= ~(FMODE_PREAD | FMODE_PWRITE);
file->private_data = inode->i_private;
return 0;
}
static int kgsl_dbgfs_release(struct inode *inode, struct file *file)
{
return 0;
}
static int kgsl_hex_dump(const char *prefix, int c, uint8_t *data,
int rowc, int linec, char __user *buff)
{
int ss;
/* Prefix of 20 chars max, 32 bytes per row, in groups of four - that's
* 8 groups at 8 chars per group plus a space, plus new-line, plus
* ending character */
char linebuf[20 + 64 + 1 + 1];
ss = snprintf(linebuf, sizeof(linebuf), prefix, c);
hex_dump_to_buffer(data, linec, rowc, 4, linebuf+ss,
sizeof(linebuf)-ss, 0);
strlcat(linebuf, "\n", sizeof(linebuf));
linebuf[sizeof(linebuf)-1] = 0;
ss = strlen(linebuf);
if (copy_to_user(buff, linebuf, ss+1))
return -EFAULT;
return ss;
}
static int kgsl_regread_nolock(struct kgsl_device *device,
unsigned int offsetwords, unsigned int *value)
{
unsigned int *reg;
if (offsetwords*sizeof(uint32_t) >= device->regspace.sizebytes) {
KGSL_DRV_ERR(device, "invalid offset %d\n", offsetwords);
return -ERANGE;
}
reg = (unsigned int *)(device->regspace.mmio_virt_base
+ (offsetwords << 2));
*value = __raw_readl(reg);
return 0;
}
static ssize_t kgsl_istore_read(
struct file *file,
char __user *buff,
size_t buff_count,
loff_t *ppos)
{
int i, count, remaining, pos = 0, tot = 0;
struct kgsl_device *device = file->private_data;
const int rowc = 8;
struct adreno_device *adreno_dev;
if (!ppos || !device)
return 0;
adreno_dev = ADRENO_DEVICE(device);
count = adreno_dev->istore_size * ADRENO_ISTORE_WORDS;
remaining = count;
for (i = 0; i < count; i += rowc) {
unsigned int vals[rowc];
int j, ss;
int linec = min(remaining, rowc);
remaining -= rowc;
if (pos >= *ppos) {
for (j = 0; j < linec; ++j)
kgsl_regread_nolock(device,
ADRENO_ISTORE_START + i + j,
vals + j);
} else
memset(vals, 0, sizeof(vals));
ss = kgsl_hex_dump("IS: %04x: ", i, (uint8_t *)vals, rowc*4,
linec*4, buff);
if (ss < 0)
return ss;
if (pos >= *ppos) {
if (tot+ss >= buff_count)
return tot;
tot += ss;
buff += ss;
*ppos += ss;
}
pos += ss;
}
return tot;
}
static const struct file_operations kgsl_istore_fops = {
.open = kgsl_dbgfs_open,
.release = kgsl_dbgfs_release,
.read = kgsl_istore_read,
.llseek = default_llseek,
};
typedef void (*reg_read_init_t)(struct kgsl_device *device);
typedef void (*reg_read_fill_t)(struct kgsl_device *device, int i,
unsigned int *vals, int linec);
static ssize_t kgsl_reg_read(struct kgsl_device *device, int count,
reg_read_init_t reg_read_init,
reg_read_fill_t reg_read_fill, const char *prefix, char __user *buff,
loff_t *ppos)
{
int i, remaining;
const int rowc = 8;
if (!ppos || *ppos || !device)
return 0;
mutex_lock(&device->mutex);
reg_read_init(device);
remaining = count;
for (i = 0; i < count; i += rowc) {
unsigned int vals[rowc];
int ss;
int linec = min(remaining, rowc);
remaining -= rowc;
reg_read_fill(device, i, vals, linec);
ss = kgsl_hex_dump(prefix, i, (uint8_t *)vals, rowc*4, linec*4,
buff);
if (ss < 0) {
mutex_unlock(&device->mutex);
return ss;
}
buff += ss;
*ppos += ss;
}
mutex_unlock(&device->mutex);
return *ppos;
}
static void kgsl_sx_reg_read_init(struct kgsl_device *device)
{
kgsl_regwrite(device, REG_RBBM_PM_OVERRIDE2, 0xFF);
kgsl_regwrite(device, REG_RBBM_DEBUG_CNTL, 0);
}
static void kgsl_sx_reg_read_fill(struct kgsl_device *device, int i,
unsigned int *vals, int linec)
{
int j;
for (j = 0; j < linec; ++j) {
kgsl_regwrite(device, REG_RBBM_DEBUG_CNTL, 0x1B00 | i);
kgsl_regread(device, REG_RBBM_DEBUG_OUT, vals+j);
}
}
static ssize_t kgsl_sx_debug_read(
struct file *file,
char __user *buff,
size_t buff_count,
loff_t *ppos)
{
struct kgsl_device *device = file->private_data;
return kgsl_reg_read(device, 0x1B, kgsl_sx_reg_read_init,
kgsl_sx_reg_read_fill, "SX: %02x: ", buff, ppos);
}
static const struct file_operations kgsl_sx_debug_fops = {
.open = kgsl_dbgfs_open,
.release = kgsl_dbgfs_release,
.read = kgsl_sx_debug_read,
};
static void kgsl_cp_reg_read_init(struct kgsl_device *device)
{
kgsl_regwrite(device, REG_RBBM_DEBUG_CNTL, 0);
}
static void kgsl_cp_reg_read_fill(struct kgsl_device *device, int i,
unsigned int *vals, int linec)
{
int j;
for (j = 0; j < linec; ++j) {
kgsl_regwrite(device, REG_RBBM_DEBUG_CNTL, 0x1628);
kgsl_regread(device, REG_RBBM_DEBUG_OUT, vals+j);
msleep(100);
}
}
static ssize_t kgsl_cp_debug_read(
struct file *file,
char __user *buff,
size_t buff_count,
loff_t *ppos)
{
struct kgsl_device *device = file->private_data;
return kgsl_reg_read(device, 20, kgsl_cp_reg_read_init,
kgsl_cp_reg_read_fill,
"CP: %02x: ", buff, ppos);
}
static const struct file_operations kgsl_cp_debug_fops = {
.open = kgsl_dbgfs_open,
.release = kgsl_dbgfs_release,
.read = kgsl_cp_debug_read,
};
static void kgsl_mh_reg_read_init(struct kgsl_device *device)
{
kgsl_regwrite(device, REG_RBBM_DEBUG_CNTL, 0);
}
static void kgsl_mh_reg_read_fill(struct kgsl_device *device, int i,
unsigned int *vals, int linec)
{
int j;
for (j = 0; j < linec; ++j) {
kgsl_regwrite(device, MH_DEBUG_CTRL, i+j);
kgsl_regread(device, MH_DEBUG_DATA, vals+j);
}
}
static ssize_t kgsl_mh_debug_read(
struct file *file,
char __user *buff,
size_t buff_count,
loff_t *ppos)
{
struct kgsl_device *device = file->private_data;
return kgsl_reg_read(device, 0x40, kgsl_mh_reg_read_init,
kgsl_mh_reg_read_fill,
"MH: %02x: ", buff, ppos);
}
static const struct file_operations kgsl_mh_debug_fops = {
.open = kgsl_dbgfs_open,
.release = kgsl_dbgfs_release,
.read = kgsl_mh_debug_read,
};
void adreno_debugfs_init(struct kgsl_device *device)
{
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
if (!device->d_debugfs || IS_ERR(device->d_debugfs))
return;
debugfs_create_file("istore", 0400, device->d_debugfs, device,
&kgsl_istore_fops);
debugfs_create_file("sx_debug", 0400, device->d_debugfs, device,
&kgsl_sx_debug_fops);
debugfs_create_file("cp_debug", 0400, device->d_debugfs, device,
&kgsl_cp_debug_fops);
debugfs_create_file("mh_debug", 0400, device->d_debugfs, device,
&kgsl_mh_debug_fops);
debugfs_create_file("cff_dump", 0644, device->d_debugfs, device,
&kgsl_cff_dump_enable_fops);
debugfs_create_u32("wait_timeout", 0644, device->d_debugfs,
&adreno_dev->wait_timeout);
/* Create post mortem control files */
pm_d_debugfs = debugfs_create_dir("postmortem", device->d_debugfs);
if (IS_ERR(pm_d_debugfs))
return;
debugfs_create_file("dump", 0600, pm_d_debugfs, device,
&pm_dump_fops);
debugfs_create_file("regs_enabled", 0644, pm_d_debugfs, device,
&pm_regs_enabled_fops);
}