[Nouveau] [PATCH] pmu/gk20a: PMU boot support.
Deepak Goyal
dgoyal at nvidia.com
Tue Mar 10 23:33:51 PDT 2015
It adds PMU boot support.It loads PMU
firmware into PMU falcon.RM/Kernel driver
receives INIT ack (through interrupt mechanism)
from PMU when PMU boots with success.
Signed-off-by: Deepak Goyal <dgoyal at nvidia.com>
---
drm/nouveau/include/nvkm/subdev/pmu.h | 26 +-
drm/nouveau/nvkm/subdev/pmu/base.c | 108 ++
drm/nouveau/nvkm/subdev/pmu/gk20a.c | 2131 ++++++++++++++++++++++++++++++++-
drm/nouveau/nvkm/subdev/pmu/gk20a.h | 369 ++++++
drm/nouveau/nvkm/subdev/pmu/priv.h | 264 ++++
5 files changed, 2884 insertions(+), 14 deletions(-)
create mode 100644 drm/nouveau/nvkm/subdev/pmu/gk20a.h
diff --git a/drm/nouveau/include/nvkm/subdev/pmu.h b/drm/nouveau/include/nvkm/subdev/pmu.h
index 7b86acc634a0..659b4e0ba02b 100644
--- a/drm/nouveau/include/nvkm/subdev/pmu.h
+++ b/drm/nouveau/include/nvkm/subdev/pmu.h
@@ -1,7 +1,20 @@
#ifndef __NVKM_PMU_H__
#define __NVKM_PMU_H__
#include <core/subdev.h>
+#include <core/device.h>
+#include <subdev/mmu.h>
+#include <linux/debugfs.h>
+struct pmu_buf_desc {
+ struct nvkm_gpuobj *pmubufobj;
+ struct nvkm_vma pmubufvma;
+ size_t size;
+};
+struct pmu_priv_vm {
+ struct nvkm_gpuobj *mem;
+ struct nvkm_gpuobj *pgd;
+ struct nvkm_vm *vm;
+};
struct nvkm_pmu {
struct nvkm_subdev base;
@@ -20,9 +33,20 @@ struct nvkm_pmu {
u32 message;
u32 data[2];
} recv;
-
+ wait_queue_head_t init_wq;
+ bool gr_initialised;
+ struct dentry *debugfs;
+ struct pmu_buf_desc *pg_buf;
+ struct pmu_priv_vm *pmuvm;
int (*message)(struct nvkm_pmu *, u32[2], u32, u32, u32, u32);
void (*pgob)(struct nvkm_pmu *, bool);
+ int (*pmu_mutex_acquire)(struct nvkm_pmu *, u32 id, u32 *token);
+ int (*pmu_mutex_release)(struct nvkm_pmu *, u32 id, u32 *token);
+ int (*pmu_load_norm)(struct nvkm_pmu *pmu, u32 *load);
+ int (*pmu_load_update)(struct nvkm_pmu *pmu);
+ void (*pmu_reset_load_counters)(struct nvkm_pmu *pmu);
+ void (*pmu_get_load_counters)(struct nvkm_pmu *pmu, u32 *busy_cycles,
+ u32 *total_cycles);
};
static inline struct nvkm_pmu *
diff --git a/drm/nouveau/nvkm/subdev/pmu/base.c b/drm/nouveau/nvkm/subdev/pmu/base.c
index 054b2d2eec35..6afd389b9764 100644
--- a/drm/nouveau/nvkm/subdev/pmu/base.c
+++ b/drm/nouveau/nvkm/subdev/pmu/base.c
@@ -25,6 +25,114 @@
#include <subdev/timer.h>
+/* init allocator struct */
+int nvkm_pmu_allocator_init(struct nvkm_pmu_allocator *allocator,
+ const char *name, u32 start, u32 len)
+{
+ memset(allocator, 0, sizeof(struct nvkm_pmu_allocator));
+
+ strncpy(allocator->name, name, 32);
+
+ allocator->base = start;
+ allocator->limit = start + len - 1;
+
+ allocator->bitmap = kcalloc(BITS_TO_LONGS(len), sizeof(long),
+ GFP_KERNEL);
+ if (!allocator->bitmap)
+ return -ENOMEM;
+
+ allocator_dbg(allocator, "%s : base %d, limit %d",
+ allocator->name, allocator->base);
+
+ init_rwsem(&allocator->rw_sema);
+
+ allocator->alloc = nvkm_pmu_allocator_block_alloc;
+ allocator->free = nvkm_pmu_allocator_block_free;
+
+ return 0;
+}
+
+/* destroy allocator, free all remaining blocks if any */
+void nvkm_pmu_allocator_destroy(struct nvkm_pmu_allocator *allocator)
+{
+ down_write(&allocator->rw_sema);
+
+ kfree(allocator->bitmap);
+
+ memset(allocator, 0, sizeof(struct nvkm_pmu_allocator));
+}
+
+/*
+ * *addr != ~0 for fixed address allocation. if *addr == 0, base addr is
+ * returned to caller in *addr.
+ *
+ * contiguous allocation, which allocates one block of
+ * contiguous address.
+*/
+int nvkm_pmu_allocator_block_alloc(struct nvkm_pmu_allocator *allocator,
+ u32 *addr, u32 len, u32 align)
+{
+ unsigned long _addr;
+
+ allocator_dbg(allocator, "[in] addr %d, len %d", *addr, len);
+
+ if ((*addr != 0 && *addr < allocator->base) || /* check addr range */
+ *addr + len > allocator->limit || /* check addr range */
+ *addr & (align - 1) || /* check addr alignment */
+ len == 0) /* check len */
+ return -EINVAL;
+
+ len = ALIGN(len, align);
+ if (!len)
+ return -ENOMEM;
+
+ down_write(&allocator->rw_sema);
+
+ _addr = bitmap_find_next_zero_area(allocator->bitmap,
+ allocator->limit - allocator->base + 1,
+ *addr ? (*addr - allocator->base) : 0,
+ len,
+ align - 1);
+ if ((_addr > allocator->limit - allocator->base + 1) ||
+ (*addr && *addr != (_addr + allocator->base))) {
+ up_write(&allocator->rw_sema);
+ return -ENOMEM;
+ }
+
+ bitmap_set(allocator->bitmap, _addr, len);
+ *addr = allocator->base + _addr;
+
+ up_write(&allocator->rw_sema);
+
+ allocator_dbg(allocator, "[out] addr %d, len %d", *addr, len);
+
+ return 0;
+}
+
+/* free all blocks between start and end */
+int nvkm_pmu_allocator_block_free(struct nvkm_pmu_allocator *allocator,
+ u32 addr, u32 len, u32 align)
+{
+ allocator_dbg(allocator, "[in] addr %d, len %d", addr, len);
+
+ if (addr + len > allocator->limit || /* check addr range */
+ addr < allocator->base ||
+ addr & (align - 1)) /* check addr alignment */
+ return -EINVAL;
+
+ len = ALIGN(len, align);
+ if (!len)
+ return -EINVAL;
+
+ down_write(&allocator->rw_sema);
+ bitmap_clear(allocator->bitmap, addr - allocator->base, len);
+ up_write(&allocator->rw_sema);
+
+ allocator_dbg(allocator, "[out] addr %d, len %d", addr, len);
+
+ return 0;
+}
+
void
nvkm_pmu_pgob(struct nvkm_pmu *pmu, bool enable)
{
diff --git a/drm/nouveau/nvkm/subdev/pmu/gk20a.c b/drm/nouveau/nvkm/subdev/pmu/gk20a.c
index a49934bbe637..0fd2530301a3 100644
--- a/drm/nouveau/nvkm/subdev/pmu/gk20a.c
+++ b/drm/nouveau/nvkm/subdev/pmu/gk20a.c
@@ -20,21 +20,67 @@
* DEALINGS IN THE SOFTWARE.
*/
#include "priv.h"
+#include "gk20a.h"
+#include <core/client.h>
+#include <core/gpuobj.h>
+#include <subdev/bar.h>
+#include <subdev/fb.h>
+#include <subdev/mc.h>
+#include <subdev/timer.h>
+#include <subdev/mmu.h>
+#include <subdev/pmu.h>
+#include <engine/falcon.h>
+#include <linux/delay.h> /* for mdelay */
+#include <linux/firmware.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/dma-mapping.h>
+#include <linux/uaccess.h>
#include <subdev/clk.h>
#include <subdev/timer.h>
#include <subdev/volt.h>
#define BUSY_SLOT 0
#define CLK_SLOT 7
+#define GK20A_PMU_UCODE_IMAGE "gpmu_ucode.bin"
+
+static int falc_trace_show(struct seq_file *s, void *data);
+static int falc_trace_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, falc_trace_show, inode->i_private);
+}
+static const struct file_operations falc_trace_fops = {
+ .open = falc_trace_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+struct pmu_priv_vm pmuvm;
+const struct firmware *pmufw;
+
+static void gk20a_pmu_isr(struct nvkm_pmu *ppmu);
+static void pmu_process_message(struct work_struct *work);
+
+static int
+gk20a_pmu_init_vm(struct nvkm_pmu *ppmu, const struct firmware *fw);
+static void
+gk20a_pmu_dump_firmware_info(struct nvkm_pmu *ppmu, const struct firmware *fw);
+
+static int
+gk20a_pmu_load_firmware(struct nvkm_pmu *ppmu, const struct firmware **pfw);
+static int gk20a_init_pmu_setup_sw(struct nvkm_pmu *ppmu);
+static int gk20a_init_pmu_setup_hw1(struct nvkm_pmu *ppmu, struct nvkm_mc *pmc);
+static void gk20a_pmu_intr(struct nvkm_subdev *subdev);
+static void gk20a_pmu_pgob(struct nvkm_pmu *ppmu, bool enable);
struct gk20a_pmu_dvfs_data {
int p_load_target;
int p_load_max;
int p_smooth;
unsigned int avg_load;
};
-
struct gk20a_pmu_priv {
struct nvkm_pmu base;
struct nvkm_alarm alarm;
@@ -46,7 +92,30 @@ struct gk20a_pmu_dvfs_dev_status {
unsigned long busy;
int cur_state;
};
-
+int gk20a_pmu_debugfs_init(struct nvkm_pmu *ppmu)
+{
+ struct dentry *d;
+ ppmu->debugfs = debugfs_create_dir("PMU", NULL);
+ if (!ppmu->debugfs)
+ goto err_out;
+ nv_debug(ppmu, "PMU directory created with success\n");
+ d = debugfs_create_file(
+ "falc_trace", 0644, ppmu->debugfs, ppmu,
+ &falc_trace_fops);
+ if (!d)
+ goto err_out;
+ return 0;
+err_out:
+ pr_err("%s: Failed to make debugfs node\n", __func__);
+ debugfs_remove_recursive(ppmu->debugfs);
+ return -ENOMEM;
+}
+void gk20a_pmu_release_firmware(struct nvkm_pmu *ppmu,
+ const struct firmware *pfw)
+{
+ nv_debug(ppmu, "firmware released\n");
+ release_firmware(pfw);
+}
static int
gk20a_pmu_dvfs_target(struct gk20a_pmu_priv *priv, int *state)
{
@@ -164,31 +233,145 @@ gk20a_pmu_fini(struct nvkm_object *object, bool suspend)
{
struct nvkm_pmu *pmu = (void *)object;
struct gk20a_pmu_priv *priv = (void *)pmu;
-
+ nv_wr32(pmu, 0x10a014, 0x00000060);
+ flush_work(&pmu->recv.work);
nvkm_timer_alarm_cancel(priv, &priv->alarm);
return nvkm_subdev_fini(&pmu->base, suspend);
}
+static bool find_hex_in_string(char *strings, u32 *hex_pos)
+{
+ u32 i = 0, j = strlen(strings);
+ for (; i < j; i++) {
+ if (strings[i] == '%')
+ if (strings[i + 1] == 'x' || strings[i + 1] == 'X') {
+ *hex_pos = i;
+ return true;
+ }
+ }
+ *hex_pos = -1;
+ return false;
+}
+static int falc_trace_show(struct seq_file *s, void *data)
+{
+ struct nvkm_pmu *ppmu = s->private;
+ struct nvkm_pmu_impl *impl = (void *)nv_oclass(ppmu);
+ struct pmu_desc *pmu = &impl->pmudata;
+ u32 i = 0, j = 0, k, l, m;
+ char part_str[40];
+ u32 data1;
+ char *log_data = kmalloc(GK20A_PMU_TRACE_BUFSIZE, GFP_KERNEL);
+ char *trace = log_data;
+ u32 *trace1 = (u32 *)log_data;
+ for (i = 0; i < GK20A_PMU_TRACE_BUFSIZE; i += 4) {
+ data1 = nv_ro32(pmu->trace_buf.pmubufobj, 0x0000 + i);
+ memcpy(log_data + i, (void *)(&data1), 32);
+ }
+ for (i = 0; i < GK20A_PMU_TRACE_BUFSIZE; i += 0x40) {
+ for (j = 0; j < 0x40; j++)
+ if (trace1[(i / 4) + j])
+ break;
+ if (j == 0x40)
+ goto out;
+ seq_printf(s, "Index %x: ", trace1[(i / 4)]);
+ l = 0;
+ m = 0;
+ while (find_hex_in_string((trace+i+20+m), &k)) {
+ if (k >= 40)
+ break;
+ strncpy(part_str, (trace+i+20+m), k);
+ part_str[k] = 0;
+ seq_printf(s, "%s0x%x", part_str,
+ trace1[(i / 4) + 1 + l]);
+ l++;
+ m += k + 2;
+ }
+ seq_printf(s, "%s", (trace+i+20+m));
+ }
+out:
+ kfree(log_data);
+ return 0;
+}
int
gk20a_pmu_init(struct nvkm_object *object)
{
- struct nvkm_pmu *pmu = (void *)object;
- struct gk20a_pmu_priv *priv = (void *)pmu;
+ struct nvkm_pmu *ppmu = (void *)object;
+ struct nvkm_mc *pmc = nvkm_mc(object);
+ struct nvkm_pmu_impl *impl = (void *)nv_oclass(ppmu);
+ struct pmu_desc *pmu;
+ struct gk20a_pmu_priv *priv;
+ struct pmu_gk20a_data *gk20adata;
int ret;
- ret = nvkm_subdev_init(&pmu->base);
+ pmu = &impl->pmudata;
+
+ nv_subdev(ppmu)->intr = gk20a_pmu_intr;
+
+ mutex_init(&pmu->isr_mutex);
+ mutex_init(&pmu->pmu_copy_lock);
+ mutex_init(&pmu->pmu_seq_lock);
+
+ if (pmufw == NULL) {
+ ret = gk20a_pmu_load_firmware(ppmu, &pmufw);
+ if (ret < 0) {
+ nv_error(ppmu, "failed to load pmu fimware\n");
+ return ret;
+ }
+ nv_debug(ppmu, "loading firmware sucessful\n");
+ ret = gk20a_pmu_init_vm(ppmu, pmufw);
+ if (ret < 0) {
+ nv_error(ppmu, "failed to map pmu fw to va space\n");
+ goto init_vm_err;
+ }
+ }
+ pmu->desc = (struct pmu_ucode_desc *)pmufw->data;
+ gk20a_pmu_dump_firmware_info(ppmu, pmufw);
+
+ if (pmu->desc->app_version != APP_VERSION_GK20A) {
+ nv_error(ppmu,
+ "PMU code version not supported version: %d\n",
+ pmu->desc->app_version);
+ ret = -EINVAL;
+ goto app_ver_err;
+ }
+ gk20adata = kzalloc(sizeof(*gk20adata), GFP_KERNEL);
+ if (!gk20adata) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ pmu->pmu_chip_data = (void *)gk20adata;
+
+ pmu->remove_support = gk20a_remove_pmu_support;
+
+ ret = gk20a_init_pmu_setup_sw(ppmu);
if (ret)
- return ret;
+ goto err;
+
+ pmu->pmu_state = PMU_STATE_STARTING;
+ ret = gk20a_init_pmu_setup_hw1(ppmu, pmc);
+ if (ret)
+ goto err;
+
+ priv = (void *)ppmu;
- pmu->pgob = nvkm_pmu_pgob;
+ ret = nvkm_subdev_init(&ppmu->base);
+ if (ret)
+ goto err;
+
+ ppmu->pgob = nvkm_pmu_pgob;
- /* init pwr perf counter */
- nv_wr32(pmu, 0x10a504 + (BUSY_SLOT * 0x10), 0x00200001);
- nv_wr32(pmu, 0x10a50c + (BUSY_SLOT * 0x10), 0x00000002);
- nv_wr32(pmu, 0x10a50c + (CLK_SLOT * 0x10), 0x00000003);
+ /* init pmu perf counter */
+ nv_wr32(ppmu, 0x10a504 + (BUSY_SLOT * 0x10), 0x00200001);
+ nv_wr32(ppmu, 0x10a50c + (BUSY_SLOT * 0x10), 0x00000002);
+ nv_wr32(ppmu, 0x10a50c + (CLK_SLOT * 0x10), 0x00000003);
- nvkm_timer_alarm(pmu, 2000000000, &priv->alarm);
+ nvkm_timer_alarm(ppmu, 2000000000, &priv->alarm);
+err:
+init_vm_err:
+app_ver_err:
+ gk20a_pmu_release_firmware(ppmu, pmufw);
return ret;
}
@@ -226,4 +409,1926 @@ gk20a_pmu_oclass = &(struct nvkm_pmu_impl) {
.init = gk20a_pmu_init,
.fini = gk20a_pmu_fini,
},
+ .base.handle = NV_SUBDEV(PMU, 0xea),
+ .pgob = gk20a_pmu_pgob,
}.base;
+void pmu_copy_from_dmem(struct pmu_desc *pmu,
+ u32 src, u8 *dst, u32 size, u8 port)
+{
+ u32 i, words, bytes;
+ u32 data, addr_mask;
+ u32 *dst_u32 = (u32 *)dst;
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+
+ if (size == 0) {
+ nv_error(ppmu, "size is zero\n");
+ goto out;
+ }
+
+ if (src & 0x3) {
+ nv_error(ppmu, "src (0x%08x) not 4-byte aligned\n", src);
+ goto out;
+ }
+
+ mutex_lock(&pmu->pmu_copy_lock);
+
+ words = size >> 2;
+ bytes = size & 0x3;
+
+ addr_mask = (0x3f << 2) | 0xff << 8;
+
+ src &= addr_mask;
+
+ nv_wr32(ppmu, (0x10a1c0 + (port * 8)), (src | (0x1 << 25)));
+
+ for (i = 0; i < words; i++) {
+ dst_u32[i] = nv_rd32(ppmu, (0x0010a1c4 + port * 8));
+ nv_debug(ppmu, "0x%08x\n", dst_u32[i]);
+ }
+ if (bytes > 0) {
+ data = nv_rd32(ppmu, (0x0010a1c4 + port * 8));
+ nv_debug(ppmu, "0x%08x\n", data);
+
+ for (i = 0; i < bytes; i++)
+ dst[(words << 2) + i] = ((u8 *)&data)[i];
+ }
+ mutex_unlock(&pmu->pmu_copy_lock);
+out:
+ nv_debug(ppmu, "exit %s\n", __func__);
+}
+
+void pmu_copy_to_dmem(struct pmu_desc *pmu,
+ u32 dst, u8 *src, u32 size, u8 port)
+{
+ u32 i, words, bytes;
+ u32 data, addr_mask;
+ u32 *src_u32 = (u32 *)src;
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+
+ if (size == 0) {
+ nv_error(ppmu, "size is zero\n");
+ goto out;
+ }
+
+ if (dst & 0x3) {
+ nv_error(ppmu, "dst (0x%08x) not 4-byte aligned\n", dst);
+ goto out;
+ }
+
+ mutex_lock(&pmu->pmu_copy_lock);
+
+ words = size >> 2;
+ bytes = size & 0x3;
+
+ addr_mask = (0x3f << 2) | 0xff << 8;
+
+ dst &= addr_mask;
+
+ nv_wr32(ppmu, (0x10a1c0 + (port * 8)), (dst | (0x1 << 24)));
+
+ for (i = 0; i < words; i++) {
+ nv_wr32(ppmu, (0x10a1c4 + (port * 8)), src_u32[i]);
+ nv_debug(ppmu, "0x%08x\n", src_u32[i]);
+ }
+ if (bytes > 0) {
+ data = 0;
+ for (i = 0; i < bytes; i++)
+ ((u8 *)&data)[i] = src[(words << 2) + i];
+ nv_wr32(ppmu, (0x10a1c4 + (port * 8)), data);
+ nv_debug(ppmu, "0x%08x\n", data);
+ }
+
+ data = nv_rd32(ppmu, (0x10a1c0 + (port * 8))) & addr_mask;
+ size = ALIGN(size, 4);
+ if (data != dst + size) {
+ nv_error(ppmu, "copy failed. bytes written %d, expected %d",
+ data - dst, size);
+ }
+ mutex_unlock(&pmu->pmu_copy_lock);
+out:
+ nv_debug(ppmu, "exit %s", __func__);
+}
+
+static int pmu_idle(struct nvkm_pmu *ppmu)
+{
+ unsigned long end_jiffies = jiffies +
+ msecs_to_jiffies(2000);
+ u32 idle_stat;
+
+ /* wait for pmu idle */
+ do {
+ idle_stat = nv_rd32(ppmu, 0x0010a04c);
+
+ if (((idle_stat & 0x01) == 0) &&
+ ((idle_stat >> 1) & 0x7fff) == 0) {
+ break;
+ }
+
+ if (time_after_eq(jiffies, end_jiffies)) {
+ nv_error(ppmu, "timeout waiting pmu idle : 0x%08x",
+ idle_stat);
+ return -EBUSY;
+ }
+ usleep_range(100, 200);
+ } while (1);
+
+ return 0;
+}
+
+void pmu_enable_irq(struct nvkm_pmu *ppmu, struct nvkm_mc *pmc,
+ bool enable)
+{
+
+ nv_wr32(pmc, 0x00000640,
+ nv_rd32(pmc, 0x00000640) &
+ ~0x1000000);
+ nv_wr32(pmc, 0x00000644,
+ nv_rd32(pmc, 0x00000644) &
+ ~0x1000000);
+ nv_wr32(ppmu, 0x0010a014, 0xff);
+
+ if (enable) {
+ nv_debug(ppmu, "enable pmu irq\n");
+ /* dest 0=falcon, 1=host; level 0=irq0, 1=irq1
+ nv_wr32(ppmu, 0x0010a01c, 0xff01ff52);
+ 0=disable, 1=enable*/
+
+ nv_wr32(ppmu, 0x0010a010, 0xff);
+ nv_wr32(pmc, 0x00000640,
+ nv_rd32(pmc, 0x00000640) |
+ 0x1000000);
+ nv_wr32(pmc, 0x00000644,
+ nv_rd32(pmc, 0x00000644) |
+ 0x1000000);
+ } else {
+ nv_debug(ppmu, "disable pmu irq\n");
+ }
+
+}
+
+static int pmu_enable_hw(struct nvkm_pmu *ppmu, struct nvkm_mc *pmc,
+ bool enable)
+{
+ u32 reg;
+
+ if (enable) {
+ int retries = GK20A_IDLE_CHECK_MAX / GK20A_IDLE_CHECK_DEFAULT;
+ /*need a spinlock?*/
+ reg = nv_rd32(pmc, 0x00000200);
+ reg |= 0x2000;
+ nv_wr32(pmc, 0x00000200, reg);
+ nv_rd32(pmc, 0x00000200);
+ do {
+ u32 w = nv_rd32(ppmu, 0x0010a10c) & 0x6;
+
+ if (!w)
+ return 0;
+
+ udelay(GK20A_IDLE_CHECK_DEFAULT);
+ } while (--retries);
+
+ reg = nv_rd32(pmc, 0x00000200);
+ reg &= ~0x2000;
+ nv_wr32(pmc, 0x00000200, reg);
+ nv_error(ppmu, "Falcon mem scrubbing timeout\n");
+
+ goto error;
+ } else {
+ reg = nv_rd32(pmc, 0x00000200);
+ reg &= ~0x2000;
+ nv_wr32(pmc, 0x00000200, reg);
+ return 0;
+ }
+error:
+ return -ETIMEDOUT;
+}
+
+static int pmu_enable(struct nvkm_pmu *ppmu, struct nvkm_mc *pmc,
+ bool enable)
+{
+ u32 pmc_enable;
+ int err;
+
+ if (!enable) {
+ pmc_enable = nv_rd32(pmc, 0x200);
+ if ((pmc_enable & 0x2000) != 0x0) {
+ pmu_enable_irq(ppmu, pmc, false);
+ pmu_enable_hw(ppmu, pmc, false);
+ }
+ } else {
+ err = pmu_enable_hw(ppmu, pmc, true);
+ if (err)
+ return err;
+
+ /* TBD: post reset */
+
+ err = pmu_idle(ppmu);
+ if (err)
+ return err;
+
+ pmu_enable_irq(ppmu, pmc, true);
+ }
+
+ return 0;
+}
+
+int pmu_reset(struct nvkm_pmu *ppmu, struct nvkm_mc *pmc)
+{
+ int err;
+
+ err = pmu_idle(ppmu);
+ if (err)
+ return err;
+
+ /* TBD: release pmu hw mutex */
+
+ err = pmu_enable(ppmu, pmc, false);
+ if (err)
+ return err;
+
+ err = pmu_enable(ppmu, pmc, true);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int pmu_bootstrap(struct pmu_desc *pmu)
+{
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+ struct pmu_ucode_desc *desc = pmu->desc;
+ u64 addr_code, addr_data, addr_load;
+ u32 i, blocks, addr_args;
+ u32 *adr_data, *adr_load, *adr_code;
+ struct pmu_cmdline_args_gk20a cmdline_args;
+ struct pmu_priv_vm *ppmuvm = &pmuvm;
+
+ nv_wr32(ppmu, 0x0010a048,
+ nv_rd32(ppmu, 0x0010a048) | 0x01);
+ /*bind the address*/
+ nv_wr32(ppmu, 0x0010a480,
+ ppmuvm->mem->addr >> 12 |
+ 0x1 << 30 |
+ 0x20000000);
+
+ /* TBD: load all other surfaces */
+ cmdline_args.falc_trace_size = GK20A_PMU_TRACE_BUFSIZE;
+ cmdline_args.falc_trace_dma_base =
+ u64_lo32(pmu->trace_buf.pmubufvma.offset >> 8);
+ cmdline_args.falc_trace_dma_idx = GK20A_PMU_DMAIDX_VIRT;
+ cmdline_args.cpu_freq_hz = 204;
+ cmdline_args.secure_mode = 0;
+
+ addr_args = (nv_rd32(ppmu, 0x0010a108) >> 9) & 0x1ff;
+ addr_args = addr_args << GK20A_PMU_DMEM_BLKSIZE2;
+ addr_args -= sizeof(struct pmu_cmdline_args_gk20a);
+ nv_debug(ppmu, "initiating copy to dmem\n");
+ pmu_copy_to_dmem(pmu, addr_args,
+ (u8 *)&cmdline_args,
+ sizeof(struct pmu_cmdline_args_gk20a), 0);
+
+ nv_wr32(ppmu, 0x0010a1c0, 0x1 << 24);
+
+
+ addr_code = u64_lo32((pmu->ucode.pmubufvma.offset +
+ desc->app_start_offset +
+ desc->app_resident_code_offset) >> 8);
+
+ addr_data = u64_lo32((pmu->ucode.pmubufvma.offset +
+ desc->app_start_offset +
+ desc->app_resident_data_offset) >> 8);
+
+ addr_load = u64_lo32((pmu->ucode.pmubufvma.offset +
+ desc->bootloader_start_offset) >> 8);
+
+ adr_code = (u32 *) (&addr_code);
+ adr_load = (u32 *) (&addr_load);
+ adr_data = (u32 *) (&addr_data);
+ nv_wr32(ppmu, 0x0010a1c4, GK20A_PMU_DMAIDX_UCODE);
+ nv_debug(ppmu, "0x%08x\n", GK20A_PMU_DMAIDX_UCODE);
+ nv_wr32(ppmu, 0x0010a1c4, *(adr_code));
+ nv_debug(ppmu, "0x%08x\n", *(adr_code));
+ nv_wr32(ppmu, 0x0010a1c4, desc->app_size);
+ nv_debug(ppmu, "0x%08x\n", desc->app_size);
+ nv_wr32(ppmu, 0x0010a1c4, desc->app_resident_code_size);
+ nv_debug(ppmu, "0x%08x\n", desc->app_resident_code_size);
+ nv_wr32(ppmu, 0x0010a1c4, desc->app_imem_entry);
+ nv_debug(ppmu, "0x%08x\n", desc->app_imem_entry);
+ nv_wr32(ppmu, 0x0010a1c4, *(adr_data));
+ nv_debug(ppmu, "0x%08x\n", *(adr_data));
+ nv_wr32(ppmu, 0x0010a1c4, desc->app_resident_data_size);
+ nv_debug(ppmu, "0x%08x\n", desc->app_resident_data_size);
+ nv_wr32(ppmu, 0x0010a1c4, *(adr_code));
+ nv_debug(ppmu, "0x%08x\n", *(adr_code));
+ nv_wr32(ppmu, 0x0010a1c4, 0x1);
+ nv_debug(ppmu, "0x%08x\n", 1);
+ nv_wr32(ppmu, 0x0010a1c4, addr_args);
+ nv_debug(ppmu, "0x%08x\n", addr_args);
+
+
+ nv_wr32(ppmu, 0x0010a110,
+ *(adr_load) - (desc->bootloader_imem_offset >> 8));
+
+ blocks = ((desc->bootloader_size + 0xFF) & ~0xFF) >> 8;
+
+ for (i = 0; i < blocks; i++) {
+ nv_wr32(ppmu, 0x0010a114,
+ desc->bootloader_imem_offset + (i << 8));
+ nv_wr32(ppmu, 0x0010a11c,
+ desc->bootloader_imem_offset + (i << 8));
+ nv_wr32(ppmu, 0x0010a118,
+ 0x01 << 4 |
+ 0x06 << 8 |
+ ((GK20A_PMU_DMAIDX_UCODE & 0x07) << 12));
+ }
+
+
+ nv_wr32(ppmu, 0x0010a104,
+ (0xffffffff & desc->bootloader_entry_point));
+
+ nv_wr32(ppmu, 0x0010a100, 0x1 << 1);
+
+ nv_wr32(ppmu, 0x0010a080, desc->app_version);
+
+ return 0;
+}
+
+void pmu_seq_init(struct pmu_desc *pmu)
+{
+ u32 i;
+
+ memset(pmu->seq, 0,
+ sizeof(struct pmu_sequence) * PMU_MAX_NUM_SEQUENCES);
+ memset(pmu->pmu_seq_tbl, 0,
+ sizeof(pmu->pmu_seq_tbl));
+
+ for (i = 0; i < PMU_MAX_NUM_SEQUENCES; i++)
+ pmu->seq[i].id = i;
+}
+
+static int pmu_seq_acquire(struct pmu_desc *pmu,
+ struct pmu_sequence **pseq)
+{
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+ struct pmu_sequence *seq;
+ u32 index;
+
+ mutex_lock(&pmu->pmu_seq_lock);
+ index = find_first_zero_bit(pmu->pmu_seq_tbl,
+ sizeof(pmu->pmu_seq_tbl));
+ if (index >= sizeof(pmu->pmu_seq_tbl)) {
+ nv_error(ppmu,
+ "no free sequence available");
+ mutex_unlock(&pmu->pmu_seq_lock);
+ return -EAGAIN;
+ }
+ set_bit(index, pmu->pmu_seq_tbl);
+ mutex_unlock(&pmu->pmu_seq_lock);
+
+ seq = &pmu->seq[index];
+ seq->state = PMU_SEQ_STATE_PENDING;
+
+ *pseq = seq;
+ return 0;
+}
+
+static void pmu_seq_release(struct pmu_desc *pmu,
+ struct pmu_sequence *seq)
+{
+ seq->state = PMU_SEQ_STATE_FREE;
+ seq->desc = PMU_INVALID_SEQ_DESC;
+ seq->callback = NULL;
+ seq->cb_params = NULL;
+ seq->msg = NULL;
+ seq->out_payload = NULL;
+ seq->in_gk20a.alloc.dmem.size = 0;
+ seq->out_gk20a.alloc.dmem.size = 0;
+ clear_bit(seq->id, pmu->pmu_seq_tbl);
+}
+
+static int pmu_queue_init(struct pmu_desc *pmu,
+ u32 id, struct pmu_init_msg_pmu_gk20a *init)
+{
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+ struct pmu_queue *queue = &pmu->queue[id];
+
+ queue->id = id;
+ queue->index = init->queue_info[id].index;
+ queue->offset = init->queue_info[id].offset;
+ queue->size = init->queue_info[id].size;
+ queue->mutex_id = id;
+ mutex_init(&queue->mutex);
+
+ nv_debug(ppmu, "queue %d: index %d, offset 0x%08x, size 0x%08x",
+ id, queue->index, queue->offset, queue->size);
+
+ return 0;
+}
+
+static int pmu_queue_head(struct pmu_desc *pmu, struct pmu_queue *queue,
+ u32 *head, bool set)
+{
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+
+ BUG_ON(!head);
+
+ if (PMU_IS_COMMAND_QUEUE(queue->id)) {
+
+ if (queue->index >= 0x00000004)
+ return -EINVAL;
+
+ if (!set)
+ *head = nv_rd32(ppmu, 0x0010a4a0 + (queue->index * 4)) &
+ 0xffffffff;
+ else
+ nv_wr32(ppmu,
+ (0x0010a4a0 + (queue->index * 4)),
+ (*head & 0xffffffff));
+ } else {
+ if (!set)
+ *head = nv_rd32(ppmu, 0x0010a4c8) & 0xffffffff;
+ else
+ nv_wr32(ppmu, 0x0010a4c8, (*head & 0xffffffff));
+ }
+
+ return 0;
+}
+
+static int pmu_queue_tail(struct pmu_desc *pmu, struct pmu_queue *queue,
+ u32 *tail, bool set)
+{
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+
+ BUG_ON(!tail);
+
+ if (PMU_IS_COMMAND_QUEUE(queue->id)) {
+
+ if (queue->index >= 0x00000004)
+ return -EINVAL;
+
+ if (!set)
+ *tail = nv_rd32(ppmu, 0x0010a4b0 + (queue->index * 4)) &
+ 0xffffffff;
+ else
+ nv_wr32(ppmu, (0x0010a4b0 + (queue->index * 4)),
+ (*tail & 0xffffffff));
+ } else {
+ if (!set)
+ *tail = nv_rd32(ppmu, 0x0010a4cc) & 0xffffffff;
+ else
+ nv_wr32(ppmu, 0x0010a4cc, (*tail & 0xffffffff));
+ }
+
+ return 0;
+}
+
+static inline void pmu_queue_read(struct pmu_desc *pmu,
+ u32 offset, u8 *dst, u32 size)
+{
+ pmu_copy_from_dmem(pmu, offset, dst, size, 0);
+}
+
+static inline void pmu_queue_write(struct pmu_desc *pmu,
+ u32 offset, u8 *src, u32 size)
+{
+ pmu_copy_to_dmem(pmu, offset, src, size, 0);
+}
+
+int pmu_mutex_acquire(struct nvkm_pmu *ppmu, u32 id, u32 *token)
+{
+ struct nvkm_pmu_impl *impl = (void *)nv_oclass(ppmu);
+ struct pmu_desc *pmu = &impl->pmudata;
+ struct pmu_mutex *mutex;
+ u32 data, owner, max_retry;
+
+ if (!pmu->initialized)
+ return -EINVAL;
+
+ BUG_ON(!token);
+ BUG_ON(!PMU_MUTEX_ID_IS_VALID(id));
+ BUG_ON(id > pmu->mutex_cnt);
+
+ mutex = &pmu->mutex[id];
+
+ owner = nv_rd32(ppmu, 0x0010a580 + (mutex->index * 4)) & 0xff;
+
+ if (*token != PMU_INVALID_MUTEX_OWNER_ID && *token == owner) {
+ BUG_ON(mutex->ref_cnt == 0);
+ nv_debug(ppmu, "already acquired by owner : 0x%08x", *token);
+ mutex->ref_cnt++;
+ return 0;
+ }
+
+ max_retry = 40;
+ do {
+ data = nv_rd32(ppmu, 0x0010a488) & 0xff;
+ if (data == 0x00000000 ||
+ data == 0x000000ff) {
+ nv_warn(ppmu,
+ "fail to generate mutex token: val 0x%08x",
+ owner);
+ usleep_range(20, 40);
+ continue;
+ }
+
+ owner = data;
+ nv_wr32(ppmu, (0x0010a580 + mutex->index * 4),
+ owner & 0xff);
+
+ data = nv_rd32(ppmu, 0x0010a580 + (mutex->index * 4));
+
+ if (owner == data) {
+ mutex->ref_cnt = 1;
+ nv_debug(ppmu, "mutex acquired: id=%d, token=0x%x",
+ mutex->index, *token);
+ *token = owner;
+ goto out;
+ } else {
+ nv_debug(ppmu, "fail to acquire mutex idx=0x%08x",
+ mutex->index);
+
+ nv_mask(ppmu, 0x0010a48c, 0xff, (owner & 0xff));
+
+ usleep_range(20, 40);
+ continue;
+ }
+ } while (max_retry-- > 0);
+
+ return -EBUSY;
+out:
+ return 0;
+}
+
+int pmu_mutex_release(struct nvkm_pmu *ppmu, u32 id, u32 *token)
+{
+ struct nvkm_pmu_impl *impl = (void *)nv_oclass(ppmu);
+ struct pmu_desc *pmu = &impl->pmudata;
+ struct pmu_mutex *mutex;
+ u32 owner;
+
+ if (!pmu->initialized)
+ return -EINVAL;
+
+ BUG_ON(!token);
+ BUG_ON(!PMU_MUTEX_ID_IS_VALID(id));
+ BUG_ON(id > pmu->mutex_cnt);
+
+ mutex = &pmu->mutex[id];
+
+ owner = nv_rd32(ppmu, 0x0010a580 + (mutex->index * 4)) & 0xff;
+
+ if (*token != owner) {
+ nv_error(ppmu,
+ "requester 0x%08x NOT match owner 0x%08x",
+ *token, owner);
+ return -EINVAL;
+ }
+
+ if (--mutex->ref_cnt > 0)
+ return -EBUSY;
+
+ nv_wr32(ppmu, 0x0010a580 + (mutex->index * 4), 0x00);
+
+ nv_mask(ppmu, 0x0010a48c, 0xff, (owner & 0xff));
+
+ nv_debug(ppmu, "mutex released: id=%d, token=0x%x",
+ mutex->index, *token);
+
+ return 0;
+}
+
+static int pmu_queue_lock(struct pmu_desc *pmu,
+ struct pmu_queue *queue)
+{
+ int ret;
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+
+ if (PMU_IS_MESSAGE_QUEUE(queue->id))
+ return 0;
+
+ if (PMU_IS_SW_COMMAND_QUEUE(queue->id)) {
+ mutex_lock(&queue->mutex);
+ return 0;
+ }
+
+ ret = pmu_mutex_acquire(ppmu, queue->mutex_id, &queue->mutex_lock);
+ return ret;
+}
+
+static int pmu_queue_unlock(struct pmu_desc *pmu,
+ struct pmu_queue *queue)
+{
+ int ret;
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+
+ if (PMU_IS_MESSAGE_QUEUE(queue->id))
+ return 0;
+
+ if (PMU_IS_SW_COMMAND_QUEUE(queue->id)) {
+ mutex_unlock(&queue->mutex);
+ return 0;
+ }
+
+ ret = pmu_mutex_release(ppmu, queue->mutex_id, &queue->mutex_lock);
+ return ret;
+}
+
+/* called by pmu_read_message, no lock */
+static bool pmu_queue_is_empty(struct pmu_desc *pmu,
+ struct pmu_queue *queue)
+{
+ u32 head, tail;
+
+ pmu_queue_head(pmu, queue, &head, QUEUE_GET);
+ if (queue->opened && queue->oflag == OFLAG_READ)
+ tail = queue->position;
+ else
+ pmu_queue_tail(pmu, queue, &tail, QUEUE_GET);
+
+ return head == tail;
+}
+
+static bool pmu_queue_has_room(struct pmu_desc *pmu,
+ struct pmu_queue *queue, u32 size, bool *need_rewind)
+{
+ u32 head, tail, free;
+ bool rewind = false;
+
+ size = ALIGN(size, QUEUE_ALIGNMENT);
+
+ pmu_queue_head(pmu, queue, &head, QUEUE_GET);
+ pmu_queue_tail(pmu, queue, &tail, QUEUE_GET);
+
+ if (head >= tail) {
+ free = queue->offset + queue->size - head;
+ free -= PMU_CMD_HDR_SIZE;
+
+ if (size > free) {
+ rewind = true;
+ head = queue->offset;
+ }
+ }
+
+ if (head < tail)
+ free = tail - head - 1;
+
+ if (need_rewind)
+ *need_rewind = rewind;
+
+ return size <= free;
+}
+
+static int pmu_queue_push(struct pmu_desc *pmu,
+ struct pmu_queue *queue, void *data, u32 size)
+{
+
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+ if (!queue->opened && queue->oflag == OFLAG_WRITE) {
+ nv_error(ppmu, "queue not opened for write\n");
+ return -EINVAL;
+ }
+
+ pmu_queue_write(pmu, queue->position, data, size);
+ queue->position += ALIGN(size, QUEUE_ALIGNMENT);
+ return 0;
+}
+
+static int pmu_queue_pop(struct pmu_desc *pmu,
+ struct pmu_queue *queue, void *data, u32 size,
+ u32 *bytes_read)
+{
+ u32 head, tail, used;
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+
+ *bytes_read = 0;
+
+ if (!queue->opened && queue->oflag == OFLAG_READ) {
+ nv_error(ppmu, "queue not opened for read\n");
+ return -EINVAL;
+ }
+
+ pmu_queue_head(pmu, queue, &head, QUEUE_GET);
+ tail = queue->position;
+
+ if (head == tail)
+ return 0;
+
+ if (head > tail)
+ used = head - tail;
+ else
+ used = queue->offset + queue->size - tail;
+
+ if (size > used) {
+ nv_warn(ppmu, "queue size smaller than request read\n");
+ size = used;
+ }
+
+ pmu_queue_read(pmu, tail, data, size);
+ queue->position += ALIGN(size, QUEUE_ALIGNMENT);
+ *bytes_read = size;
+ return 0;
+}
+
+static void pmu_queue_rewind(struct pmu_desc *pmu,
+ struct pmu_queue *queue)
+{
+ struct pmu_cmd cmd;
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+
+
+ if (!queue->opened) {
+ nv_error(ppmu, "queue not opened\n");
+ goto out;
+ }
+
+ if (queue->oflag == OFLAG_WRITE) {
+ cmd.hdr.unit_id = PMU_UNIT_REWIND;
+ cmd.hdr.size = PMU_CMD_HDR_SIZE;
+ pmu_queue_push(pmu, queue, &cmd, cmd.hdr.size);
+ nv_debug(ppmu, "queue %d rewinded\n", queue->id);
+ }
+
+ queue->position = queue->offset;
+out:
+ nv_debug(ppmu, "exit %s\n", __func__);
+}
+
+/* open for read and lock the queue */
+static int pmu_queue_open_read(struct pmu_desc *pmu,
+ struct pmu_queue *queue)
+{
+ int err;
+
+ err = pmu_queue_lock(pmu, queue);
+ if (err)
+ return err;
+
+ if (queue->opened)
+ BUG();
+
+ pmu_queue_tail(pmu, queue, &queue->position, QUEUE_GET);
+ queue->oflag = OFLAG_READ;
+ queue->opened = true;
+
+ return 0;
+}
+
+/* open for write and lock the queue
+ make sure there's enough free space for the write */
+static int pmu_queue_open_write(struct pmu_desc *pmu,
+ struct pmu_queue *queue, u32 size)
+{
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+ bool rewind = false;
+ int err;
+
+ err = pmu_queue_lock(pmu, queue);
+ if (err)
+ return err;
+
+ if (queue->opened)
+ BUG();
+
+ if (!pmu_queue_has_room(pmu, queue, size, &rewind)) {
+ nv_error(ppmu, "queue full");
+ pmu_queue_unlock(pmu, queue);
+ return -EAGAIN;
+ }
+
+ pmu_queue_head(pmu, queue, &queue->position, QUEUE_GET);
+ queue->oflag = OFLAG_WRITE;
+ queue->opened = true;
+
+ if (rewind)
+ pmu_queue_rewind(pmu, queue);
+
+ return 0;
+}
+
+/* close and unlock the queue */
+static int pmu_queue_close(struct pmu_desc *pmu,
+ struct pmu_queue *queue, bool commit)
+{
+ if (!queue->opened)
+ return 0;
+
+ if (commit) {
+ if (queue->oflag == OFLAG_READ) {
+ pmu_queue_tail(pmu, queue,
+ &queue->position, QUEUE_SET);
+ } else {
+ pmu_queue_head(pmu, queue,
+ &queue->position, QUEUE_SET);
+ }
+ }
+
+ queue->opened = false;
+
+ pmu_queue_unlock(pmu, queue);
+
+ return 0;
+}
+
+int pmu_wait_message_cond(struct pmu_desc *pmu, u32 timeout,
+ u32 *var, u32 val)
+{
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+ unsigned long end_jiffies = jiffies + msecs_to_jiffies(timeout);
+ unsigned long delay = GK20A_IDLE_CHECK_DEFAULT;
+
+ do {
+ if (*var == val)
+ return 0;
+
+ if (nv_rd32(ppmu, 0x0010a008))
+ gk20a_pmu_isr(ppmu);
+
+ usleep_range(delay, delay * 2);
+ delay = min_t(u32, delay << 1, GK20A_IDLE_CHECK_MAX);
+ } while (time_before(jiffies, end_jiffies));
+
+ return -ETIMEDOUT;
+}
+
+void pmu_dump_falcon_stats(struct pmu_desc *pmu)
+{
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+ int i;
+
+ nv_debug(ppmu, "pmu_falcon_os_r : %d\n",
+ nv_rd32(ppmu, 0x0010a080));
+ nv_debug(ppmu, "pmu_falcon_cpuctl_r : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a100));
+ nv_debug(ppmu, "pmu_falcon_idlestate_r : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a04c));
+ nv_debug(ppmu, "pmu_falcon_mailbox0_r : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a040));
+ nv_debug(ppmu, "pmu_falcon_mailbox1_r : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a044));
+ nv_debug(ppmu, "pmu_falcon_irqstat_r : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a008));
+ nv_debug(ppmu, "pmu_falcon_irqmode_r : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a00c));
+ nv_debug(ppmu, "pmu_falcon_irqmask_r : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a018));
+ nv_debug(ppmu, "pmu_falcon_irqdest_r : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a01c));
+
+ for (i = 0; i < 0x0000000c; i++)
+ nv_debug(ppmu, "pmu_pmu_mailbox_r(%d) : 0x%x\n",
+ i, nv_rd32(ppmu, 0x0010a450 + i*4));
+
+ for (i = 0; i < 0x00000004; i++)
+ nv_debug(ppmu, "pmu_pmu_debug_r(%d) : 0x%x\n",
+ i, nv_rd32(ppmu, 0x0010a5c0 + i*4));
+
+ for (i = 0; i < 6/*NV_Ppmu_FALCON_ICD_IDX_RSTAT__SIZE_1*/; i++) {
+ nv_wr32(ppmu, 0x0010a200,
+ 0xe |
+ (i & 0x1f) << 8);
+ nv_debug(ppmu, "pmu_rstat (%d) : 0x%x\n",
+ i, nv_rd32(ppmu, 0x0010a20c));
+ }
+
+ i = nv_rd32(ppmu, 0x0010a7b0);
+ nv_debug(ppmu, "pmu_pmu_bar0_error_status_r : 0x%x\n", i);
+ if (i != 0) {
+ nv_debug(ppmu, "pmu_pmu_bar0_addr_r : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a7a0));
+ nv_debug(ppmu, "pmu_pmu_bar0_data_r : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a7a4));
+ nv_debug(ppmu, "pmu_pmu_bar0_timeout_r : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a7a8));
+ nv_debug(ppmu, "pmu_pmu_bar0_ctl_r : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a7ac));
+ }
+
+ i = nv_rd32(ppmu, 0x0010a988);
+ nv_debug(ppmu, "pmu_pmu_bar0_fecs_error_r : 0x%x\n", i);
+
+ i = nv_rd32(ppmu, 0x0010a16c);
+ nv_debug(ppmu, "pmu_falcon_exterrstat_r : 0x%x\n", i);
+ if (((i >> 31) & 0x1)) {
+ nv_debug(ppmu, "pmu_falcon_exterraddr_r : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a168));
+ /*nv_debug(ppmu, "pmc_enable : 0x%x\n",
+ nv_rd32(pmc, 0x00000200));*/
+ }
+
+ nv_debug(ppmu, "pmu_falcon_engctl_r : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a0a4));
+ nv_debug(ppmu, "pmu_falcon_curctx_r : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a050));
+ nv_debug(ppmu, "pmu_falcon_nxtctx_r : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a054));
+
+ nv_wr32(ppmu, 0x0010a200,
+ 0x8 |
+ ((PMU_FALCON_REG_IMB & 0x1f) << 8));
+ nv_debug(ppmu, "PMU_FALCON_REG_IMB : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a20c));
+
+ nv_wr32(ppmu, 0x0010a200,
+ 0x8 |
+ ((PMU_FALCON_REG_DMB & 0x1f) << 8));
+ nv_debug(ppmu, "PMU_FALCON_REG_DMB : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a20c));
+
+ nv_wr32(ppmu, 0x0010a200,
+ 0x8 |
+ ((PMU_FALCON_REG_CSW & 0x1f) << 8));
+ nv_debug(ppmu, "PMU_FALCON_REG_CSW : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a20c));
+
+ nv_wr32(ppmu, 0x0010a200,
+ 0x8 |
+ ((PMU_FALCON_REG_CTX & 0x1f) << 8));
+ nv_debug(ppmu, "PMU_FALCON_REG_CTX : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a20c));
+
+ nv_wr32(ppmu, 0x0010a200,
+ 0x8 |
+ ((PMU_FALCON_REG_EXCI & 0x1f) << 8));
+ nv_debug(ppmu, "PMU_FALCON_REG_EXCI : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a20c));
+
+ for (i = 0; i < 4; i++) {
+ nv_wr32(ppmu, 0x0010a200,
+ 0x8 |
+ ((PMU_FALCON_REG_PC & 0x1f) << 8));
+ nv_debug(ppmu, "PMU_FALCON_REG_PC : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a20c));
+
+ nv_wr32(ppmu, 0x0010a200,
+ 0x8 |
+ ((PMU_FALCON_REG_SP & 0x1f) << 8));
+ nv_debug(ppmu, "PMU_FALCON_REG_SP : 0x%x\n",
+ nv_rd32(ppmu, 0x0010a20c));
+ }
+
+ /* PMU may crash due to FECS crash. Dump FECS status */
+ /*gk20a_fecs_dump_falcon_stats(g);*/
+}
+
+static bool pmu_validate_cmd(struct pmu_desc *pmu, struct pmu_cmd *cmd,
+ struct pmu_msg *msg, struct pmu_payload *payload,
+ u32 queue_id)
+{
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+ struct pmu_queue *queue;
+ u32 in_size, out_size;
+
+ nv_debug(ppmu, "pmu validate cmd\n");
+ pmu_dump_falcon_stats(pmu);
+
+ if (!PMU_IS_SW_COMMAND_QUEUE(queue_id))
+ goto invalid_cmd;
+
+ queue = &pmu->queue[queue_id];
+ if (cmd->hdr.size < PMU_CMD_HDR_SIZE)
+ goto invalid_cmd;
+
+ if (cmd->hdr.size > (queue->size >> 1))
+ goto invalid_cmd;
+
+ if (msg != NULL && msg->hdr.size < PMU_MSG_HDR_SIZE)
+ goto invalid_cmd;
+
+ if (!PMU_UNIT_ID_IS_VALID(cmd->hdr.unit_id))
+ goto invalid_cmd;
+
+ if (payload == NULL)
+ return true;
+
+ if (payload->in.buf == NULL && payload->out.buf == NULL)
+ goto invalid_cmd;
+
+ if ((payload->in.buf != NULL && payload->in.size == 0) ||
+ (payload->out.buf != NULL && payload->out.size == 0))
+ goto invalid_cmd;
+
+ in_size = PMU_CMD_HDR_SIZE;
+ if (payload->in.buf) {
+ in_size += payload->in.offset;
+ in_size += sizeof(struct pmu_allocation_gk20a);
+ }
+
+ out_size = PMU_CMD_HDR_SIZE;
+ if (payload->out.buf) {
+ out_size += payload->out.offset;
+ out_size += sizeof(struct pmu_allocation_gk20a);
+ }
+
+ if (in_size > cmd->hdr.size || out_size > cmd->hdr.size)
+ goto invalid_cmd;
+
+
+ if ((payload->in.offset != 0 && payload->in.buf == NULL) ||
+ (payload->out.offset != 0 && payload->out.buf == NULL))
+ goto invalid_cmd;
+
+ return true;
+
+invalid_cmd:
+ nv_error(ppmu, "invalid pmu cmd :\n"
+ "queue_id=%d,\n"
+ "cmd_size=%d, cmd_unit_id=%d, msg=%p, msg_size=%d,\n"
+ "payload in=%p, in_size=%d, in_offset=%d,\n"
+ "payload out=%p, out_size=%d, out_offset=%d",
+ queue_id, cmd->hdr.size, cmd->hdr.unit_id,
+ msg, msg ? msg->hdr.unit_id : ~0,
+ &payload->in, payload->in.size, payload->in.offset,
+ &payload->out, payload->out.size, payload->out.offset);
+
+ return false;
+}
+
+static int pmu_write_cmd(struct pmu_desc *pmu, struct pmu_cmd *cmd,
+ u32 queue_id, unsigned long timeout)
+{
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+ struct pmu_queue *queue;
+ unsigned long end_jiffies = jiffies +
+ msecs_to_jiffies(timeout);
+ int err;
+
+ nv_debug(ppmu, "pmu write cmd\n");
+
+ queue = &pmu->queue[queue_id];
+
+ do {
+ err = pmu_queue_open_write(pmu, queue, cmd->hdr.size);
+ if (err == -EAGAIN && time_before(jiffies, end_jiffies))
+ usleep_range(1000, 2000);
+ else
+ break;
+ } while (1);
+
+ if (err)
+ goto clean_up;
+
+ pmu_queue_push(pmu, queue, cmd, cmd->hdr.size);
+
+ err = pmu_queue_close(pmu, queue, true);
+
+clean_up:
+ if (err)
+ nv_error(ppmu,
+ "fail to write cmd to queue %d", queue_id);
+ else
+ nv_debug(ppmu, "cmd writing done");
+
+ return err;
+}
+
+int gk20a_pmu_cmd_post(struct nvkm_pmu *ppmu, struct pmu_cmd *cmd,
+ struct pmu_msg *msg, struct pmu_payload *payload,
+ u32 queue_id, pmu_callback callback, void *cb_param,
+ u32 *seq_desc, unsigned long timeout)
+{
+ struct nvkm_pmu_impl *impl = (void *)nv_oclass(ppmu);
+ struct pmu_desc *pmu = &impl->pmudata;
+ struct pmu_sequence *seq;
+ struct pmu_allocation_gk20a *in = NULL, *out = NULL;
+ int err;
+
+ BUG_ON(!cmd);
+ BUG_ON(!seq_desc);
+ BUG_ON(!pmu->pmu_ready);
+ nv_debug(ppmu, "Post CMD\n");
+ if (!pmu_validate_cmd(pmu, cmd, msg, payload, queue_id))
+ return -EINVAL;
+
+ err = pmu_seq_acquire(pmu, &seq);
+ if (err)
+ return err;
+
+ cmd->hdr.seq_id = seq->id;
+
+ cmd->hdr.ctrl_flags = 0;
+ cmd->hdr.ctrl_flags |= PMU_CMD_FLAGS_STATUS;
+ cmd->hdr.ctrl_flags |= PMU_CMD_FLAGS_INTR;
+
+ seq->callback = callback;
+ seq->cb_params = cb_param;
+ seq->msg = msg;
+ seq->out_payload = NULL;
+ seq->desc = pmu->next_seq_desc++;
+
+ if (payload)
+ seq->out_payload = payload->out.buf;
+
+ *seq_desc = seq->desc;
+
+ if (payload && payload->in.offset != 0) {
+ in = (struct pmu_allocation_gk20a *)((u8 *)&cmd->cmd +
+ payload->in.offset);
+
+ if (payload->in.buf != payload->out.buf)
+ in->alloc.dmem.size = (u16)payload->in.size;
+ else
+ in->alloc.dmem.size =
+ (u16)max(payload->in.size, payload->out.size);
+
+ err = pmu->dmem.alloc(&pmu->dmem,
+ (void *)&in->alloc.dmem.offset,
+ in->alloc.dmem.size,
+ PMU_DMEM_ALLOC_ALIGNMENT);
+ if (err)
+ goto clean_up;
+
+ pmu_copy_to_dmem(pmu, (in->alloc.dmem.offset),
+ payload->in.buf, payload->in.size, 0);
+ seq->in_gk20a.alloc.dmem.size = in->alloc.dmem.size;
+ seq->in_gk20a.alloc.dmem.offset = in->alloc.dmem.offset;
+ }
+
+ if (payload && payload->out.offset != 0) {
+ out = (struct pmu_allocation_gk20a *)((u8 *)&cmd->cmd +
+ payload->out.offset);
+ out->alloc.dmem.size = (u16)payload->out.size;
+
+ if (payload->out.buf != payload->in.buf) {
+ err = pmu->dmem.alloc(&pmu->dmem,
+ (void *)&out->alloc.dmem.offset,
+ out->alloc.dmem.size,
+ PMU_DMEM_ALLOC_ALIGNMENT);
+ if (err)
+ goto clean_up;
+ } else {
+ BUG_ON(in == NULL);
+ out->alloc.dmem.offset = in->alloc.dmem.offset;
+ }
+
+ seq->out_gk20a.alloc.dmem.size = out->alloc.dmem.size;
+ seq->out_gk20a.alloc.dmem.offset = out->alloc.dmem.offset;
+ }
+
+ seq->state = PMU_SEQ_STATE_USED;
+ err = pmu_write_cmd(pmu, cmd, queue_id, timeout);
+ if (err)
+ seq->state = PMU_SEQ_STATE_PENDING;
+
+ nv_debug(ppmu, "cmd posted\n");
+
+ return 0;
+
+clean_up:
+ nv_debug(ppmu, "cmd post failed\n");
+ if (in)
+ pmu->dmem.free(&pmu->dmem,
+ in->alloc.dmem.offset,
+ in->alloc.dmem.size,
+ PMU_DMEM_ALLOC_ALIGNMENT);
+ if (out)
+ pmu->dmem.free(&pmu->dmem,
+ out->alloc.dmem.offset,
+ out->alloc.dmem.size,
+ PMU_DMEM_ALLOC_ALIGNMENT);
+
+ pmu_seq_release(pmu, seq);
+ return err;
+}
+
+void gk20a_pmu_isr(struct nvkm_pmu *ppmu)
+{
+ struct nvkm_pmu_impl *impl = (void *)nv_oclass(ppmu);
+ struct pmu_desc *pmu = &impl->pmudata;
+ struct nvkm_mc *pmc = nvkm_mc(ppmu);
+ struct pmu_queue *queue;
+ u32 intr, mask;
+ bool recheck = false;
+ if (!pmu->isr_enabled)
+ goto out;
+
+ mask = nv_rd32(ppmu, 0x0010a018) &
+ nv_rd32(ppmu, 0x0010a01c);
+
+ intr = nv_rd32(ppmu, 0x0010a008) & mask;
+
+ nv_debug(ppmu, "received falcon interrupt: 0x%08x", intr);
+ pmu_enable_irq(ppmu, pmc, false);
+ if (!intr || pmu->pmu_state == PMU_STATE_OFF) {
+ nv_wr32(ppmu, 0x0010a004, intr);
+ nv_error(ppmu, "pmu state off\n");
+ pmu_enable_irq(ppmu, pmc, true);
+ goto out;
+ }
+ if (intr & 0x10) {
+ nv_error(ppmu,
+ "pmu halt intr not implemented");
+ pmu_dump_falcon_stats(pmu);
+ }
+ if (intr & 0x20) {
+ nv_error(ppmu,
+ "pmu exterr intr not implemented. Clearing interrupt.");
+ pmu_dump_falcon_stats(pmu);
+
+ nv_wr32(ppmu, 0x0010a16c,
+ nv_rd32(ppmu, 0x0010a16c) &
+ ~(0x1 << 31));
+ }
+ if (intr & 0x40) {
+ nv_debug(ppmu, "scheduling work\n");
+ schedule_work(&pmu->isr_workq);
+ pmu_enable_irq(ppmu, pmc, true);
+ recheck = true;
+ }
+
+ if (recheck) {
+ queue = &pmu->queue[PMU_MESSAGE_QUEUE];
+ if (!pmu_queue_is_empty(pmu, queue))
+ nv_wr32(ppmu, 0x0010a000, 0x40);
+ } else {
+ pmu_enable_irq(ppmu, pmc, true);
+ }
+
+ pmu_enable_irq(ppmu, pmc, true);
+ nv_wr32(ppmu, 0x0010a004, intr);
+out:
+ nv_debug(ppmu, "irq handled\n");
+}
+
+static int
+gk20a_pmu_init_vm(struct nvkm_pmu *ppmu, const struct firmware *fw)
+{
+ int ret = 0;
+ struct nvkm_pmu_impl *impl = (void *)nv_oclass(ppmu);
+ struct pmu_desc *pmu = &impl->pmudata;
+ u32 *ucode_image;
+ struct pmu_ucode_desc *desc = (struct pmu_ucode_desc *)fw->data;
+ int i;
+ struct pmu_priv_vm *ppmuvm = &pmuvm;
+ struct nvkm_device *device = nv_device(&ppmu->base);
+ struct nvkm_vm *vm;
+ u64 pmu_area_len = 300*1024;
+
+ ppmu->pmuvm = &pmuvm;
+ ppmu->pg_buf = &pmu->pg_buf;
+ pmu->pmu = ppmu;
+ /* mem for inst blk*/
+ ret = nvkm_gpuobj_new(nv_object(ppmu), NULL, 0x1000, 0, 0,
+ &ppmuvm->mem);
+ if (ret)
+ goto instblk_alloc_err;
+
+ /* mem for pgd*/
+ ret = nvkm_gpuobj_new(nv_object(ppmu), NULL, 0x8000, 0, 0,
+ &ppmuvm->pgd);
+ if (ret)
+ goto pgd_alloc_err;
+
+ /*allocate virtual memory range*/
+ ret = nvkm_vm_new(device, 0, pmu_area_len, 0, &vm);
+ if (ret)
+ goto virt_alloc_err;
+
+ atomic_inc(&vm->engref[NVDEV_SUBDEV_PMU]);
+ /*update VM with pgd */
+
+ ret = nvkm_vm_ref(vm, &ppmuvm->vm, ppmuvm->pgd);
+ if (ret)
+ goto virt_alloc_err;
+
+ /*update pgd in inst blk */
+ nv_wo32(ppmuvm->mem, 0x0200, lower_32_bits(ppmuvm->pgd->addr));
+ nv_wo32(ppmuvm->mem, 0x0204, upper_32_bits(ppmuvm->pgd->addr));
+ nv_wo32(ppmuvm->mem, 0x0208, lower_32_bits(pmu_area_len - 1));
+ nv_wo32(ppmuvm->mem, 0x020c, upper_32_bits(pmu_area_len - 1));
+
+ /* allocate memory for pmu fw to be copied to*/
+ ret = nvkm_gpuobj_new(nv_object(ppmu), NULL,
+ GK20A_PMU_UCODE_SIZE_MAX, 0x1000, 0, &pmu->ucode.pmubufobj);
+ if (ret)
+ goto fw_alloc_err;
+
+ ucode_image = (u32 *)((u32)desc + desc->descriptor_size);
+ for (i = 0; i < (desc->app_start_offset + desc->app_size) >> 2; i++) {
+ nv_wo32(pmu->ucode.pmubufobj, i << 2, ucode_image[i]);
+ pr_info("writing 0x%08x\n", ucode_image[i]);
+ }
+ /* map allocated memory into GMMU */
+ ret = nvkm_gpuobj_map_vm(nv_gpuobj(pmu->ucode.pmubufobj), vm,
+ NV_MEM_ACCESS_RW,
+ &pmu->ucode.pmubufvma);
+ if (ret)
+ goto map_err;
+
+ nv_debug(ppmu, "%s function end\n", __func__);
+ return ret;
+map_err:
+ nvkm_gpuobj_destroy(pmu->ucode.pmubufobj);
+virt_alloc_err:
+fw_alloc_err:
+ nvkm_gpuobj_destroy(ppmuvm->pgd);
+pgd_alloc_err:
+ nvkm_gpuobj_destroy(ppmuvm->mem);
+instblk_alloc_err:
+ return ret;
+
+}
+
+static int
+gk20a_pmu_load_firmware(struct nvkm_pmu *ppmu, const struct firmware **pfw)
+{
+ struct nvkm_device *dev;
+ char name[32];
+
+ dev = nv_device(ppmu);
+
+ snprintf(name, sizeof(name), "nvidia/tegra124/%s",
+ GK20A_PMU_UCODE_IMAGE);
+
+ return request_firmware(pfw, name, nv_device_base(dev));
+}
+
+static void
+gk20a_pmu_dump_firmware_info(struct nvkm_pmu *ppmu,
+ const struct firmware *fw)
+{
+ struct pmu_ucode_desc *desc = (struct pmu_ucode_desc *)fw->data;
+
+ nv_debug(ppmu, "GK20A PMU firmware information\n");
+ nv_debug(ppmu, "descriptor size = %u\n", desc->descriptor_size);
+ nv_debug(ppmu, "image size = %u\n", desc->image_size);
+ nv_debug(ppmu, "app_version = 0x%08x\n", desc->app_version);
+ nv_debug(ppmu, "date = %s\n", desc->date);
+ nv_debug(ppmu, "bootloader_start_offset = 0x%08x\n",
+ desc->bootloader_start_offset);
+ nv_debug(ppmu, "bootloader_size = 0x%08x\n", desc->bootloader_size);
+ nv_debug(ppmu, "bootloader_imem_offset = 0x%08x\n",
+ desc->bootloader_imem_offset);
+ nv_debug(ppmu, "bootloader_entry_point = 0x%08x\n",
+ desc->bootloader_entry_point);
+ nv_debug(ppmu, "app_start_offset = 0x%08x\n", desc->app_start_offset);
+ nv_debug(ppmu, "app_size = 0x%08x\n", desc->app_size);
+ nv_debug(ppmu, "app_imem_offset = 0x%08x\n", desc->app_imem_offset);
+ nv_debug(ppmu, "app_imem_entry = 0x%08x\n", desc->app_imem_entry);
+ nv_debug(ppmu, "app_dmem_offset = 0x%08x\n", desc->app_dmem_offset);
+ nv_debug(ppmu, "app_resident_code_offset = 0x%08x\n",
+ desc->app_resident_code_offset);
+ nv_debug(ppmu, "app_resident_code_size = 0x%08x\n",
+ desc->app_resident_code_size);
+ nv_debug(ppmu, "app_resident_data_offset = 0x%08x\n",
+ desc->app_resident_data_offset);
+ nv_debug(ppmu, "app_resident_data_size = 0x%08x\n",
+ desc->app_resident_data_size);
+ nv_debug(ppmu, "nb_overlays = %d\n", desc->nb_overlays);
+
+ nv_debug(ppmu, "compressed = %u\n", desc->compressed);
+}
+
+static int pmu_process_init_msg(struct pmu_desc *pmu,
+ struct pmu_msg *msg)
+{
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+ struct pmu_init_msg_pmu_gk20a *init;
+ struct pmu_sha1_gid_data gid_data;
+ u32 i, tail = 0;
+
+ tail = nv_rd32(ppmu, 0x0010a4cc) & 0xffffffff;
+
+ pmu_copy_from_dmem(pmu, tail,
+ (u8 *)&msg->hdr, PMU_MSG_HDR_SIZE, 0);
+
+ if (msg->hdr.unit_id != PMU_UNIT_INIT) {
+ nv_error(ppmu,
+ "expecting init msg");
+ return -EINVAL;
+ }
+
+ pmu_copy_from_dmem(pmu, tail + PMU_MSG_HDR_SIZE,
+ (u8 *)&msg->msg, msg->hdr.size - PMU_MSG_HDR_SIZE, 0);
+
+ if (msg->msg.init.msg_type != PMU_INIT_MSG_TYPE_PMU_INIT) {
+ nv_error(ppmu,
+ "expecting init msg");
+ return -EINVAL;
+ }
+
+ tail += ALIGN(msg->hdr.size, PMU_DMEM_ALIGNMENT);
+ nv_wr32(ppmu, 0x0010a4cc,
+ tail & 0xffffffff);
+
+ init = &msg->msg.init.pmu_init_gk20a;
+ if (!pmu->gid_info.valid) {
+
+ pmu_copy_from_dmem(pmu,
+ init->sw_managed_area_offset,
+ (u8 *)&gid_data,
+ sizeof(struct pmu_sha1_gid_data), 0);
+
+ pmu->gid_info.valid =
+ (*(u32 *)gid_data.signature == PMU_SHA1_GID_SIGNATURE);
+
+ if (pmu->gid_info.valid) {
+
+ BUG_ON(sizeof(pmu->gid_info.gid) !=
+ sizeof(gid_data.gid));
+
+ memcpy(pmu->gid_info.gid, gid_data.gid,
+ sizeof(pmu->gid_info.gid));
+ }
+ }
+
+ for (i = 0; i < PMU_QUEUE_COUNT; i++)
+ pmu_queue_init(pmu, i, init);
+
+ if (!pmu->dmem.alloc)
+ nvkm_pmu_allocator_init(&pmu->dmem, "gk20a_pmu_dmem",
+ init->sw_managed_area_offset,
+ init->sw_managed_area_size);
+
+ pmu->pmu_ready = true;
+ pmu->pmu_state = PMU_STATE_INIT_RECEIVED;
+
+ return 0;
+}
+
+static bool pmu_read_message(struct pmu_desc *pmu, struct pmu_queue *queue,
+ struct pmu_msg *msg, int *status)
+{
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+ u32 read_size, bytes_read;
+ int err;
+
+ *status = 0;
+
+ if (pmu_queue_is_empty(pmu, queue))
+ return false;
+
+ err = pmu_queue_open_read(pmu, queue);
+ if (err) {
+ nv_error(ppmu,
+ "fail to open queue %d for read", queue->id);
+ *status = err;
+ return false;
+ }
+
+ err = pmu_queue_pop(pmu, queue, &msg->hdr,
+ PMU_MSG_HDR_SIZE, &bytes_read);
+ if (err || bytes_read != PMU_MSG_HDR_SIZE) {
+ nv_error(ppmu,
+ "fail to read msg from queue %d", queue->id);
+ *status = err | -EINVAL;
+ goto clean_up;
+ }
+
+ if (msg->hdr.unit_id == PMU_UNIT_REWIND) {
+ pmu_queue_rewind(pmu, queue);
+ /* read again after rewind */
+ err = pmu_queue_pop(pmu, queue, &msg->hdr,
+ PMU_MSG_HDR_SIZE, &bytes_read);
+ if (err || bytes_read != PMU_MSG_HDR_SIZE) {
+ nv_error(ppmu,
+ "fail to read msg from queue %d", queue->id);
+ *status = err | -EINVAL;
+ goto clean_up;
+ }
+ }
+
+ if (!PMU_UNIT_ID_IS_VALID(msg->hdr.unit_id)) {
+ nv_error(ppmu,
+ "read invalid unit_id %d from queue %d",
+ msg->hdr.unit_id, queue->id);
+ *status = -EINVAL;
+ goto clean_up;
+ }
+
+ if (msg->hdr.size > PMU_MSG_HDR_SIZE) {
+ read_size = msg->hdr.size - PMU_MSG_HDR_SIZE;
+ err = pmu_queue_pop(pmu, queue, &msg->msg,
+ read_size, &bytes_read);
+ if (err || bytes_read != read_size) {
+ nv_error(ppmu,
+ "fail to read msg from queue %d", queue->id);
+ *status = err;
+ goto clean_up;
+ }
+ }
+
+ err = pmu_queue_close(pmu, queue, true);
+ if (err) {
+ nv_error(ppmu,
+ "fail to close queue %d", queue->id);
+ *status = err;
+ return false;
+ }
+
+ return true;
+
+clean_up:
+ err = pmu_queue_close(pmu, queue, false);
+ if (err)
+ nv_error(ppmu,
+ "fail to close queue %d", queue->id);
+ return false;
+}
+
+static int pmu_response_handle(struct pmu_desc *pmu,
+ struct pmu_msg *msg)
+{
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+ struct pmu_sequence *seq;
+ int ret = 0;
+
+ nv_debug(ppmu, "handling pmu response\n");
+ seq = &pmu->seq[msg->hdr.seq_id];
+ if (seq->state != PMU_SEQ_STATE_USED &&
+ seq->state != PMU_SEQ_STATE_CANCELLED) {
+ nv_error(ppmu,
+ "msg for an unknown sequence %d", seq->id);
+ return -EINVAL;
+ }
+
+ if (msg->hdr.unit_id == PMU_UNIT_RC &&
+ msg->msg.rc.msg_type == PMU_RC_MSG_TYPE_UNHANDLED_CMD) {
+ nv_error(ppmu,
+ "unhandled cmd: seq %d", seq->id);
+ } else if (seq->state != PMU_SEQ_STATE_CANCELLED) {
+ if (seq->msg) {
+ if (seq->msg->hdr.size >= msg->hdr.size) {
+ memcpy(seq->msg, msg, msg->hdr.size);
+ if (seq->out_gk20a.alloc.dmem.size != 0) {
+ pmu_copy_from_dmem(pmu,
+ seq->out_gk20a.alloc.dmem.offset,
+ seq->out_payload,
+ seq->out_gk20a.alloc.dmem.size, 0);
+ }
+ } else {
+ nv_error(ppmu,
+ "sequence %d msg buffer too small",
+ seq->id);
+ }
+ }
+ } else
+ seq->callback = NULL;
+ if (seq->in_gk20a.alloc.dmem.size != 0)
+ pmu->dmem.free(&pmu->dmem,
+ seq->in_gk20a.alloc.dmem.offset,
+ seq->in_gk20a.alloc.dmem.size,
+ PMU_DMEM_ALLOC_ALIGNMENT);
+ if (seq->out_gk20a.alloc.dmem.size != 0)
+ pmu->dmem.free(&pmu->dmem,
+ seq->out_gk20a.alloc.dmem.offset,
+ seq->out_gk20a.alloc.dmem.size,
+ PMU_DMEM_ALLOC_ALIGNMENT);
+
+ if (seq->callback)
+ seq->callback(ppmu, msg, seq->cb_params, seq->desc, ret);
+
+ pmu_seq_release(pmu, seq);
+
+ /* TBD: notify client waiting for available dmem */
+ nv_debug(ppmu, "pmu response processed\n");
+
+ return 0;
+}
+
+int pmu_wait_message_cond(struct pmu_desc *pmu, u32 timeout,
+ u32 *var, u32 val);
+
+
+static int pmu_handle_event(struct pmu_desc *pmu, struct pmu_msg *msg)
+{
+ int err = 0;
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+
+ switch (msg->hdr.unit_id) {
+ case PMU_UNIT_PERFMON:
+ nv_debug(ppmu, "init perfmon event generated\n");
+ break;
+ default:
+ nv_debug(ppmu, "default event generated\n");
+ break;
+ }
+
+ return err;
+}
+
+void pmu_process_message(struct work_struct *work)
+{
+ struct pmu_desc *pmu = container_of(work, struct pmu_desc, isr_workq);
+ struct pmu_msg msg;
+ int status;
+ struct nvkm_pmu *ppmu = (void *)nvkm_pmu((void *)
+ impl_from_pmu(pmu));
+ struct nvkm_mc *pmc = nvkm_mc(ppmu);
+
+ mutex_lock(&pmu->isr_mutex);
+ if (unlikely(!pmu->pmu_ready)) {
+ nv_debug(ppmu, "processing init msg\n");
+ pmu_process_init_msg(pmu, &msg);
+ mutex_unlock(&pmu->isr_mutex);
+ pmu_enable_irq(ppmu, pmc, true);
+ goto out;
+ }
+
+ while (pmu_read_message(pmu,
+ &pmu->queue[PMU_MESSAGE_QUEUE], &msg, &status)) {
+
+ nv_debug(ppmu, "read msg hdr:\n"
+ "unit_id = 0x%08x, size = 0x%08x,\n"
+ "ctrl_flags = 0x%08x, seq_id = 0x%08x\n",
+ msg.hdr.unit_id, msg.hdr.size,
+ msg.hdr.ctrl_flags, msg.hdr.seq_id);
+
+ msg.hdr.ctrl_flags &= ~PMU_CMD_FLAGS_PMU_MASK;
+
+ if (msg.hdr.ctrl_flags == PMU_CMD_FLAGS_EVENT)
+ pmu_handle_event(pmu, &msg);
+ else
+ pmu_response_handle(pmu, &msg);
+ }
+ mutex_unlock(&pmu->isr_mutex);
+ pmu_enable_irq(ppmu, pmc, true);
+out:
+ nv_debug(ppmu, "exit %s\n", __func__);
+}
+
+int gk20a_pmu_destroy(struct nvkm_pmu *ppmu, struct nvkm_mc *pmc)
+{
+ struct nvkm_pmu_impl *impl = (void *)nv_oclass(ppmu);
+ struct pmu_desc *pmu = &impl->pmudata;
+
+ /* make sure the pending operations are finished before we continue */
+ cancel_work_sync(&pmu->isr_workq);
+ pmu->initialized = false;
+
+ mutex_lock(&pmu->isr_mutex);
+ pmu_enable(ppmu, pmc, false);
+ pmu->isr_enabled = false;
+ mutex_unlock(&pmu->isr_mutex);
+
+ pmu->pmu_state = PMU_STATE_OFF;
+ pmu->pmu_ready = false;
+ pmu->zbc_ready = false;
+
+ return 0;
+}
+
+int gk20a_pmu_load_norm(struct nvkm_pmu *ppmu, u32 *load)
+{
+ struct nvkm_pmu_impl *impl = (void *)nv_oclass(ppmu);
+ struct pmu_desc *pmu = &impl->pmudata;
+ *load = pmu->load_shadow;
+ return 0;
+}
+
+int gk20a_pmu_load_update(struct nvkm_pmu *ppmu)
+{
+ struct nvkm_pmu_impl *impl = (void *)nv_oclass(ppmu);
+ struct pmu_desc *pmu = &impl->pmudata;
+ u16 _load = 0;
+
+ pmu_copy_from_dmem(pmu, pmu->sample_buffer, (u8 *)&_load, 2, 0);
+ pmu->load_shadow = _load / 10;
+ pmu->load_avg = (((9*pmu->load_avg) + pmu->load_shadow) / 10);
+
+ return 0;
+}
+
+void gk20a_pmu_get_load_counters(struct nvkm_pmu *ppmu, u32 *busy_cycles,
+ u32 *total_cycles)
+{
+ /*todo if (!g->power_on || gk20a_busy(g->dev)) {
+ *busy_cycles = 0;
+ *total_cycles = 0;
+ return;
+ }*/
+
+ *busy_cycles = nv_rd32(ppmu, 0x0010a508 + 16) & 0x7fffffff;
+ /*todormb();*/
+ *total_cycles = nv_rd32(ppmu, 0x0010a508 + 32) & 0x7fffffff;
+ /*todogk20a_idle(g->dev);*/
+}
+
+void gk20a_pmu_reset_load_counters(struct nvkm_pmu *ppmu)
+{
+ u32 reg_val = 1 << 31;
+
+ /*todoif (!g->power_on || gk20a_busy(g->dev))
+ return;*/
+
+ nv_wr32(ppmu, 0x0010a508 + 32, reg_val);
+ /*todowmb()*/;
+ nv_wr32(ppmu, 0x0010a508 + 16, reg_val);
+ /*todogk20a_idle(g->dev);*/
+}
+
+static int gk20a_init_pmu_setup_hw1(struct nvkm_pmu *ppmu, struct nvkm_mc *pmc)
+{
+ struct nvkm_pmu_impl *impl = (void *)nv_oclass(ppmu);
+ struct pmu_desc *pmu = &impl->pmudata;
+ int err;
+
+ mutex_lock(&pmu->isr_mutex);
+ pmu_reset(ppmu, pmc);
+ pmu->isr_enabled = true;
+ mutex_unlock(&pmu->isr_mutex);
+
+ /* setup apertures - virtual */
+ nv_wr32(ppmu, 0x10a600 + 0 * 4, 0x0);
+ nv_wr32(ppmu, 0x10a600 + 1 * 4, 0x0);
+ /* setup apertures - physical */
+ nv_wr32(ppmu, 0x10a600 + 2 * 4, 0x4 | 0x0);
+ nv_wr32(ppmu, 0x10a600 + 3 * 4, 0x4 | 0x1);
+ nv_wr32(ppmu, 0x10a600 + 4 * 4, 0x4 | 0x2);
+
+ /* TBD: load pmu ucode */
+ err = pmu_bootstrap(pmu);
+ if (err)
+ return err;
+
+ return 0;
+
+}
+
+static int gk20a_init_pmu_setup_sw(struct nvkm_pmu *ppmu)
+{
+ struct nvkm_pmu_impl *impl = (void *)nv_oclass(ppmu);
+ struct pmu_desc *pmu = &impl->pmudata;
+ struct pmu_priv_vm *ppmuvm = &pmuvm;
+ int i, err = 0;
+ int ret = 0;
+
+
+ if (pmu->sw_ready) {
+
+ for (i = 0; i < pmu->mutex_cnt; i++) {
+ pmu->mutex[i].id = i;
+ pmu->mutex[i].index = i;
+ }
+ pmu_seq_init(pmu);
+
+ nv_debug(ppmu, "skipping init\n");
+ goto skip_init;
+ }
+
+ /* no infoRom script from vbios? */
+
+ /* TBD: sysmon subtask */
+
+ pmu->mutex_cnt = 0x00000010;
+ pmu->mutex = kzalloc(pmu->mutex_cnt *
+ sizeof(struct pmu_mutex), GFP_KERNEL);
+ if (!pmu->mutex) {
+ err = -ENOMEM;
+ nv_error(ppmu, "not enough space ENOMEM\n");
+ goto err;
+ }
+
+ for (i = 0; i < pmu->mutex_cnt; i++) {
+ pmu->mutex[i].id = i;
+ pmu->mutex[i].index = i;
+ }
+
+ pmu->seq = kzalloc(PMU_MAX_NUM_SEQUENCES *
+ sizeof(struct pmu_sequence), GFP_KERNEL);
+ if (!pmu->seq) {
+ err = -ENOMEM;
+ nv_error(ppmu, "not enough space ENOMEM\n");
+ goto err_free_mutex;
+ }
+
+ pmu_seq_init(pmu);
+
+ INIT_WORK(&pmu->isr_workq, pmu_process_message);
+ init_waitqueue_head(&ppmu->init_wq);
+ ppmu->gr_initialised = false;
+
+ /* allocate memory for pmu fw area */
+ ret = nvkm_gpuobj_new(nv_object(ppmu), NULL, GK20A_PMU_SEQ_BUF_SIZE,
+ 0x1000, 0, &pmu->seq_buf.pmubufobj);
+ if (ret)
+ return ret;
+ ret = nvkm_gpuobj_new(nv_object(ppmu), NULL, GK20A_PMU_TRACE_BUFSIZE,
+ 0, 0, &pmu->trace_buf.pmubufobj);
+ if (ret)
+ return ret;
+ /* map allocated memory into GMMU */
+ ret = nvkm_gpuobj_map_vm(nv_gpuobj(pmu->seq_buf.pmubufobj),
+ ppmuvm->vm,
+ NV_MEM_ACCESS_RW,
+ &pmu->seq_buf.pmubufvma);
+ if (ret)
+ return ret;
+ ret = nvkm_gpuobj_map_vm(nv_gpuobj(pmu->trace_buf.pmubufobj),
+ ppmuvm->vm,
+ NV_MEM_ACCESS_RW,
+ &pmu->trace_buf.pmubufvma);
+ if (ret)
+ return ret;
+
+ /* TBD: remove this if ZBC save/restore is handled by PMU
+ * end an empty ZBC sequence for now */
+ nv_wo32(pmu->seq_buf.pmubufobj, 0, 0x16);
+ nv_wo32(pmu->seq_buf.pmubufobj, 1, 0x00);
+ nv_wo32(pmu->seq_buf.pmubufobj, 2, 0x01);
+ nv_wo32(pmu->seq_buf.pmubufobj, 3, 0x00);
+ nv_wo32(pmu->seq_buf.pmubufobj, 4, 0x00);
+ nv_wo32(pmu->seq_buf.pmubufobj, 5, 0x00);
+ nv_wo32(pmu->seq_buf.pmubufobj, 6, 0x00);
+ nv_wo32(pmu->seq_buf.pmubufobj, 7, 0x00);
+
+ pmu->seq_buf.size = GK20A_PMU_SEQ_BUF_SIZE;
+ ret = gk20a_pmu_debugfs_init(ppmu);
+ if (ret)
+ return ret;
+
+ pmu->sw_ready = true;
+
+skip_init:
+ return 0;
+err_free_mutex:
+ kfree(pmu->mutex);
+err:
+ return err;
+}
+
+static void
+gk20a_pmu_pgob(struct nvkm_pmu *ppmu, bool enable)
+{
+ /*
+ nv_mask(ppmu, 0x000200, 0x00001000, 0x00000000);
+ nv_rd32(ppmu, 0x000200);
+ nv_mask(ppmu, 0x000200, 0x08000000, 0x08000000);
+
+ msleep(50);
+
+ nv_mask(ppmu, 0x000200, 0x08000000, 0x00000000);
+ nv_mask(ppmu, 0x000200, 0x00001000, 0x00001000);
+ nv_rd32(ppmu, 0x000200);
+ */
+}
+
+static void gk20a_pmu_intr(struct nvkm_subdev *subdev)
+{
+ struct nvkm_pmu *ppmu = nvkm_pmu(subdev);
+
+ gk20a_pmu_isr(ppmu);
+}
+
+void gk20a_remove_pmu_support(struct pmu_desc *pmu)
+{
+ nvkm_pmu_allocator_destroy(&pmu->dmem);
+}
+
+int gk20a_message(struct nvkm_pmu *ppmu, u32 reply[2],
+ u32 process, u32 message, u32 data0, u32 data1)
+{
+ return -EPERM;
+}
+
+int
+gk20a_pmu_create_(struct nvkm_object *parent,
+ struct nvkm_object *engine,
+ struct nvkm_oclass *oclass, int length, void **pobject)
+{
+ struct nvkm_pmu *ppmu;
+ struct nvkm_device *device = nv_device(parent);
+ int ret;
+
+ ret = nvkm_subdev_create_(parent, engine, oclass, 0, "PPMU",
+ "pmu", length, pobject);
+ ppmu = *pobject;
+ if (ret)
+ return ret;
+
+ ret = nv_device_get_irq(device, true);
+
+ ppmu->message = gk20a_message;
+ ppmu->pgob = gk20a_pmu_pgob;
+ ppmu->pmu_mutex_acquire = pmu_mutex_acquire;
+ ppmu->pmu_mutex_release = pmu_mutex_release;
+ ppmu->pmu_load_norm = gk20a_pmu_load_norm;
+ ppmu->pmu_load_update = gk20a_pmu_load_update;
+ ppmu->pmu_reset_load_counters = gk20a_pmu_reset_load_counters;
+ ppmu->pmu_get_load_counters = gk20a_pmu_get_load_counters;
+
+ return 0;
+}
+
+
+
diff --git a/drm/nouveau/nvkm/subdev/pmu/gk20a.h b/drm/nouveau/nvkm/subdev/pmu/gk20a.h
new file mode 100644
index 000000000000..a084d6d518b4
--- /dev/null
+++ b/drm/nouveau/nvkm/subdev/pmu/gk20a.h
@@ -0,0 +1,369 @@
+#ifndef __NVKM_pmu_GK20A_H__
+#define __NVKM_pmu_GK20A_H__
+
+/*
+ * Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+void pmu_setup_hw(struct pmu_desc *pmu);
+void gk20a_remove_pmu_support(struct pmu_desc *pmu);
+#define gk20a_pmu_create(p, e, o, d) \
+ gk20a_pmu_create_((p), (e), (o), sizeof(**d), (void **)d)
+
+int gk20a_pmu_create_(struct nvkm_object *, struct nvkm_object *,
+ struct nvkm_oclass *, int, void **);
+/* defined by pmu hw spec */
+#define GK20A_PMU_VA_SIZE (512 * 1024 * 1024)
+#define GK20A_PMU_UCODE_SIZE_MAX (256 * 1024)
+#define GK20A_PMU_SEQ_BUF_SIZE 4096
+/* idle timeout */
+#define GK20A_IDLE_CHECK_DEFAULT 100 /* usec */
+#define GK20A_IDLE_CHECK_MAX 5000 /* usec */
+
+/* so far gk20a has two engines: gr and ce2(gr_copy) */
+enum {
+ ENGINE_GR_GK20A = 0,
+ ENGINE_CE2_GK20A = 1,
+ ENGINE_INVAL_GK20A
+};
+
+#define ZBC_MASK(i) (~(~(0) << ((i)+1)) & 0xfffe)
+
+#define APP_VERSION_GK20A 17997577
+
+enum {
+ GK20A_PMU_DMAIDX_UCODE = 0,
+ GK20A_PMU_DMAIDX_VIRT = 1,
+ GK20A_PMU_DMAIDX_PHYS_VID = 2,
+ GK20A_PMU_DMAIDX_PHYS_SYS_COH = 3,
+ GK20A_PMU_DMAIDX_PHYS_SYS_NCOH = 4,
+ GK20A_PMU_DMAIDX_RSVD = 5,
+ GK20A_PMU_DMAIDX_PELPG = 6,
+ GK20A_PMU_DMAIDX_END = 7
+};
+
+struct pmu_mem_gk20a {
+ u32 dma_base;
+ u8 dma_offset;
+ u8 dma_idx;
+ u16 fb_size;
+};
+
+struct pmu_dmem {
+ u16 size;
+ u32 offset;
+};
+
+struct pmu_cmdline_args_gk20a {
+ u32 cpu_freq_hz; /* Frequency of the clock driving PMU */
+ u32 falc_trace_size; /* falctrace buffer size (bytes) */
+ u32 falc_trace_dma_base; /* 256-byte block address */
+ u32 falc_trace_dma_idx; /* dmaIdx for DMA operations */
+ u8 secure_mode;
+ struct pmu_mem_gk20a gc6_ctx; /* dmem offset of gc6 context */
+};
+
+#define GK20A_PMU_TRACE_BUFSIZE 0x4000 /* 4K */
+#define GK20A_PMU_DMEM_BLKSIZE2 8
+
+#define GK20A_PMU_UCODE_NB_MAX_OVERLAY 32
+#define GK20A_PMU_UCODE_NB_MAX_DATE_LENGTH 64
+
+struct pmu_ucode_desc {
+ u32 descriptor_size;
+ u32 image_size;
+ u32 tools_version;
+ u32 app_version;
+ char date[GK20A_PMU_UCODE_NB_MAX_DATE_LENGTH];
+ u32 bootloader_start_offset;
+ u32 bootloader_size;
+ u32 bootloader_imem_offset;
+ u32 bootloader_entry_point;
+ u32 app_start_offset;
+ u32 app_size;
+ u32 app_imem_offset;
+ u32 app_imem_entry;
+ u32 app_dmem_offset;
+ u32 app_resident_code_offset; /* Offset from appStartOffset */
+/* Exact size of the resident code
+ * ( potentially contains CRC inside at the end ) */
+ u32 app_resident_code_size;
+ u32 app_resident_data_offset; /* Offset from appStartOffset */
+/* Exact size of the resident data
+ * ( potentially contains CRC inside at the end ) */
+ u32 app_resident_data_size;
+ u32 nb_overlays;
+ struct {u32 start; u32 size; } load_ovl[GK20A_PMU_UCODE_NB_MAX_OVERLAY];
+ u32 compressed;
+};
+
+#define PMU_UNIT_REWIND (0x00)
+#define PMU_UNIT_PG (0x03)
+#define PMU_UNIT_INIT (0x07)
+#define PMU_UNIT_PERFMON (0x12)
+#define PMU_UNIT_THERM (0x1B)
+#define PMU_UNIT_RC (0x1F)
+#define PMU_UNIT_NULL (0x20)
+#define PMU_UNIT_END (0x23)
+
+#define PMU_UNIT_TEST_START (0xFE)
+#define PMU_UNIT_END_SIM (0xFF)
+#define PMU_UNIT_TEST_END (0xFF)
+
+#define PMU_UNIT_ID_IS_VALID(id) \
+ (((id) < PMU_UNIT_END) || ((id) >= PMU_UNIT_TEST_START))
+
+#define PMU_DMEM_ALLOC_ALIGNMENT (32)
+#define PMU_DMEM_ALIGNMENT (4)
+
+#define PMU_CMD_FLAGS_PMU_MASK (0xF0)
+
+#define PMU_CMD_FLAGS_STATUS BIT(0)
+#define PMU_CMD_FLAGS_INTR BIT(1)
+#define PMU_CMD_FLAGS_EVENT BIT(2)
+#define PMU_CMD_FLAGS_WATERMARK BIT(3)
+
+struct pmu_hdr {
+ u8 unit_id;
+ u8 size;
+ u8 ctrl_flags;
+ u8 seq_id;
+};
+#define PMU_MSG_HDR_SIZE sizeof(struct pmu_hdr)
+#define PMU_CMD_HDR_SIZE sizeof(struct pmu_hdr)
+
+
+struct pmu_allocation_gk20a {
+ struct {
+ struct pmu_dmem dmem;
+ struct pmu_mem_gk20a fb;
+ } alloc;
+};
+
+enum {
+ PMU_INIT_MSG_TYPE_PMU_INIT = 0,
+};
+
+struct pmu_init_msg_pmu_gk20a {
+ u8 msg_type;
+ u8 pad;
+ u16 os_debug_entry_point;
+
+ struct {
+ u16 size;
+ u16 offset;
+ u8 index;
+ u8 pad;
+ } queue_info[PMU_QUEUE_COUNT];
+
+ u16 sw_managed_area_offset;
+ u16 sw_managed_area_size;
+};
+
+struct pmu_init_msg {
+ union {
+ u8 msg_type;
+ struct pmu_init_msg_pmu_gk20a pmu_init_gk20a;
+ };
+};
+
+
+enum {
+ PMU_RC_MSG_TYPE_UNHANDLED_CMD = 0,
+};
+
+struct pmu_rc_msg_unhandled_cmd {
+ u8 msg_type;
+ u8 unit_id;
+};
+
+struct pmu_rc_msg {
+ u8 msg_type;
+ struct pmu_rc_msg_unhandled_cmd unhandled_cmd;
+};
+
+/* PERFMON */
+#define PMU_DOMAIN_GROUP_PSTATE 0
+#define PMU_DOMAIN_GROUP_GPC2CLK 1
+#define PMU_DOMAIN_GROUP_NUM 2
+struct pmu_perfmon_counter_gk20a {
+ u8 index;
+ u8 flags;
+ u8 group_id;
+ u8 valid;
+ u16 upper_threshold; /* units of 0.01% */
+ u16 lower_threshold; /* units of 0.01% */
+};
+struct pmu_zbc_cmd {
+ u8 cmd_type;
+ u8 pad;
+ u16 entry_mask;
+};
+
+/* PERFMON MSG */
+enum {
+ PMU_PERFMON_MSG_ID_INCREASE_EVENT = 0,
+ PMU_PERFMON_MSG_ID_DECREASE_EVENT = 1,
+ PMU_PERFMON_MSG_ID_INIT_EVENT = 2,
+ PMU_PERFMON_MSG_ID_ACK = 3
+};
+
+struct pmu_perfmon_msg_generic {
+ u8 msg_type;
+ u8 state_id;
+ u8 group_id;
+ u8 data;
+};
+
+struct pmu_perfmon_msg {
+ union {
+ u8 msg_type;
+ struct pmu_perfmon_msg_generic gen;
+ };
+};
+
+
+struct pmu_cmd {
+ struct pmu_hdr hdr;
+ union {
+ struct pmu_zbc_cmd zbc;
+ } cmd;
+};
+
+struct pmu_msg {
+ struct pmu_hdr hdr;
+ union {
+ struct pmu_init_msg init;
+ struct pmu_perfmon_msg perfmon;
+ struct pmu_rc_msg rc;
+ } msg;
+};
+
+/* write by sw, read by pmu, protected by sw mutex lock */
+#define PMU_COMMAND_QUEUE_HPQ 0
+/* write by sw, read by pmu, protected by sw mutex lock */
+#define PMU_COMMAND_QUEUE_LPQ 1
+/* write by pmu, read by sw, accessed by interrupt handler, no lock */
+#define PMU_MESSAGE_QUEUE 4
+#define PMU_QUEUE_COUNT 5
+
+enum {
+ PMU_MUTEX_ID_RSVD1 = 0,
+ PMU_MUTEX_ID_GPUSER,
+ PMU_MUTEX_ID_GPMUTEX,
+ PMU_MUTEX_ID_I2C,
+ PMU_MUTEX_ID_RMLOCK,
+ PMU_MUTEX_ID_MSGBOX,
+ PMU_MUTEX_ID_FIFO,
+ PMU_MUTEX_ID_PG,
+ PMU_MUTEX_ID_GR,
+ PMU_MUTEX_ID_CLK,
+ PMU_MUTEX_ID_RSVD6,
+ PMU_MUTEX_ID_RSVD7,
+ PMU_MUTEX_ID_RSVD8,
+ PMU_MUTEX_ID_RSVD9,
+ PMU_MUTEX_ID_INVALID
+};
+
+#define PMU_IS_COMMAND_QUEUE(id) \
+ ((id) < PMU_MESSAGE_QUEUE)
+
+#define PMU_IS_SW_COMMAND_QUEUE(id) \
+ (((id) == PMU_COMMAND_QUEUE_HPQ) || \
+ ((id) == PMU_COMMAND_QUEUE_LPQ))
+
+#define PMU_IS_MESSAGE_QUEUE(id) \
+ ((id) == PMU_MESSAGE_QUEUE)
+
+enum {
+ OFLAG_READ = 0,
+ OFLAG_WRITE
+};
+
+#define QUEUE_SET (true)
+ /*todo find how to get cpu_pa*/
+#define QUEUE_GET (false)
+
+#define QUEUE_ALIGNMENT (4)
+
+#define PMU_PGENG_GR_BUFFER_IDX_INIT (0)
+#define PMU_PGENG_GR_BUFFER_IDX_ZBC (1)
+#define PMU_PGENG_GR_BUFFER_IDX_FECS (2)
+
+enum {
+ PMU_DMAIDX_UCODE = 0,
+ PMU_DMAIDX_VIRT = 1,
+ PMU_DMAIDX_PHYS_VID = 2,
+ PMU_DMAIDX_PHYS_SYS_COH = 3,
+ PMU_DMAIDX_PHYS_SYS_NCOH = 4,
+ PMU_DMAIDX_RSVD = 5,
+ PMU_DMAIDX_PELPG = 6,
+ PMU_DMAIDX_END = 7
+};
+
+#define PMU_MUTEX_ID_IS_VALID(id) \
+ ((id) < PMU_MUTEX_ID_INVALID)
+
+#define PMU_INVALID_MUTEX_OWNER_ID (0)
+
+struct pmu_mutex {
+ u32 id;
+ u32 index;
+ u32 ref_cnt;
+};
+
+
+#define PMU_INVALID_SEQ_DESC (~0)
+
+enum {
+ PMU_SEQ_STATE_FREE = 0,
+ PMU_SEQ_STATE_PENDING,
+ PMU_SEQ_STATE_USED,
+ PMU_SEQ_STATE_CANCELLED
+};
+
+struct pmu_payload {
+ struct {
+ void *buf;
+ u32 offset;
+ u32 size;
+ } in, out;
+};
+
+typedef void (*pmu_callback)(struct nvkm_pmu *, struct pmu_msg *, void *,
+u32, u32);
+
+struct pmu_sequence {
+ u8 id;
+ u32 state;
+ u32 desc;
+ struct pmu_msg *msg;
+ struct pmu_allocation_gk20a in_gk20a;
+ struct pmu_allocation_gk20a out_gk20a;
+ u8 *out_payload;
+ pmu_callback callback;
+ void *cb_params;
+};
+struct pmu_gk20a_data {
+ struct pmu_perfmon_counter_gk20a perfmon_counter_gk20a;
+ u32 perfmon_state_id[PMU_DOMAIN_GROUP_NUM];
+};
+
+#endif /*_GK20A_H__*/
diff --git a/drm/nouveau/nvkm/subdev/pmu/priv.h b/drm/nouveau/nvkm/subdev/pmu/priv.h
index 998410563bfd..c4686e418582 100644
--- a/drm/nouveau/nvkm/subdev/pmu/priv.h
+++ b/drm/nouveau/nvkm/subdev/pmu/priv.h
@@ -2,7 +2,91 @@
#define __NVKM_PMU_PRIV_H__
#include <subdev/pmu.h>
#include <subdev/pmu/fuc/os.h>
+#include <core/object.h>
+#include <core/device.h>
+#include <core/parent.h>
+#include <core/mm.h>
+#include <linux/rwsem.h>
+#include <linux/slab.h>
+#include <subdev/mmu.h>
+#include <core/gpuobj.h>
+static inline u32 u64_hi32(u64 n)
+{
+ return (u32)((n >> 32) & ~(u32)0);
+}
+
+static inline u32 u64_lo32(u64 n)
+{
+ return (u32)(n & ~(u32)0);
+}
+
+/* #define ALLOCATOR_DEBUG */
+
+/* main struct */
+struct nvkm_pmu_allocator {
+
+ char name[32]; /* name for allocator */
+/*struct rb_root rb_root;*/ /* rb tree root for blocks */
+
+ u32 base; /* min value of this linear space */
+ u32 limit; /* max value = limit - 1 */
+
+ unsigned long *bitmap; /* bitmap */
+
+ struct gk20a_alloc_block *block_first; /* first block in list */
+ struct gk20a_alloc_block *block_recent; /* last visited block */
+
+ u32 first_free_addr; /* first free addr, non-contigous
+ allocation preferred start,
+ in order to pick up small holes */
+ u32 last_free_addr; /* last free addr, contiguous
+ allocation preferred start */
+ u32 cached_hole_size; /* max free hole size up to
+ last_free_addr */
+ u32 block_count; /* number of blocks */
+
+ struct rw_semaphore rw_sema; /* lock */
+ struct kmem_cache *block_cache; /* slab cache */
+
+ /* if enabled, constrain to [base, limit) */
+ struct {
+ bool enable;
+ u32 base;
+ u32 limit;
+ } constraint;
+
+ int (*alloc)(struct nvkm_pmu_allocator *allocator,
+ u32 *addr, u32 len, u32 align);
+ int (*free)(struct nvkm_pmu_allocator *allocator,
+ u32 addr, u32 len, u32 align);
+
+};
+
+int nvkm_pmu_allocator_init(struct nvkm_pmu_allocator *allocator,
+ const char *name, u32 base, u32 size);
+void nvkm_pmu_allocator_destroy(struct nvkm_pmu_allocator *allocator);
+
+int nvkm_pmu_allocator_block_alloc(struct nvkm_pmu_allocator *allocator,
+ u32 *addr, u32 len, u32 align);
+
+int nvkm_pmu_allocator_block_free(struct nvkm_pmu_allocator *allocator,
+ u32 addr, u32 len, u32 align);
+
+#if defined(ALLOCATOR_DEBUG)
+
+#define allocator_dbg(alloctor, format, arg...) \
+do { \
+ if (1) \
+ pr_debug("nvkm_pmu_allocator (%s) %s: " format "\n",\
+ alloctor->name, __func__, ##arg);\
+} while (0)
+
+#else /* ALLOCATOR_DEBUG */
+
+#define allocator_dbg(format, arg...)
+
+#endif /* ALLOCATOR_DEBUG */
#define nvkm_pmu_create(p, e, o, d) \
nvkm_pmu_create_((p), (e), (o), sizeof(**d), (void **)d)
#define nvkm_pmu_destroy(p) \
@@ -26,6 +110,179 @@ int _nvkm_pmu_ctor(struct nvkm_object *, struct nvkm_object *,
int _nvkm_pmu_init(struct nvkm_object *);
int _nvkm_pmu_fini(struct nvkm_object *, bool);
void nvkm_pmu_pgob(struct nvkm_pmu *pmu, bool enable);
+#define PMU_PG_IDLE_THRESHOLD 15000
+#define PMU_PG_POST_POWERUP_IDLE_THRESHOLD 1000000
+
+/* state transition :
+ OFF => [OFF_ON_PENDING optional] => ON_PENDING => ON => OFF
+ ON => OFF is always synchronized */
+#define PMU_ELPG_STAT_OFF 0 /* elpg is off */
+#define PMU_ELPG_STAT_ON 1 /* elpg is on */
+/* elpg is off, ALLOW cmd has been sent, wait for ack */
+#define PMU_ELPG_STAT_ON_PENDING 2
+/* elpg is on, DISALLOW cmd has been sent, wait for ack */
+#define PMU_ELPG_STAT_OFF_PENDING 3
+/* elpg is off, caller has requested on, but ALLOW
+cmd hasn't been sent due to ENABLE_ALLOW delay */
+#define PMU_ELPG_STAT_OFF_ON_PENDING 4
+
+/* Falcon Register index */
+#define PMU_FALCON_REG_R0 (0)
+#define PMU_FALCON_REG_R1 (1)
+#define PMU_FALCON_REG_R2 (2)
+#define PMU_FALCON_REG_R3 (3)
+#define PMU_FALCON_REG_R4 (4)
+#define PMU_FALCON_REG_R5 (5)
+#define PMU_FALCON_REG_R6 (6)
+#define PMU_FALCON_REG_R7 (7)
+#define PMU_FALCON_REG_R8 (8)
+#define PMU_FALCON_REG_R9 (9)
+#define PMU_FALCON_REG_R10 (10)
+#define PMU_FALCON_REG_R11 (11)
+#define PMU_FALCON_REG_R12 (12)
+#define PMU_FALCON_REG_R13 (13)
+#define PMU_FALCON_REG_R14 (14)
+#define PMU_FALCON_REG_R15 (15)
+#define PMU_FALCON_REG_IV0 (16)
+#define PMU_FALCON_REG_IV1 (17)
+#define PMU_FALCON_REG_UNDEFINED (18)
+#define PMU_FALCON_REG_EV (19)
+#define PMU_FALCON_REG_SP (20)
+#define PMU_FALCON_REG_PC (21)
+#define PMU_FALCON_REG_IMB (22)
+#define PMU_FALCON_REG_DMB (23)
+#define PMU_FALCON_REG_CSW (24)
+#define PMU_FALCON_REG_CCR (25)
+#define PMU_FALCON_REG_SEC (26)
+#define PMU_FALCON_REG_CTX (27)
+#define PMU_FALCON_REG_EXCI (28)
+#define PMU_FALCON_REG_RSVD0 (29)
+#define PMU_FALCON_REG_RSVD1 (30)
+#define PMU_FALCON_REG_RSVD2 (31)
+#define PMU_FALCON_REG_SIZE (32)
+
+/* Choices for pmu_state */
+#define PMU_STATE_OFF 0 /* PMU is off */
+#define PMU_STATE_STARTING 1 /* PMU is on, but not booted */
+#define PMU_STATE_INIT_RECEIVED 2 /* PMU init message received */
+#define PMU_STATE_ELPG_BOOTING 3 /* PMU is booting */
+#define PMU_STATE_ELPG_BOOTED 4 /* ELPG is initialized */
+#define PMU_STATE_LOADING_PG_BUF 5 /* Loading PG buf */
+#define PMU_STATE_LOADING_ZBC 6 /* Loading ZBC buf */
+#define PMU_STATE_STARTED 7 /* Fully unitialized */
+
+#define PMU_QUEUE_COUNT 5
+
+#define PMU_MAX_NUM_SEQUENCES (256)
+#define PMU_SEQ_BIT_SHIFT (5)
+#define PMU_SEQ_TBL_SIZE \
+ (PMU_MAX_NUM_SEQUENCES >> PMU_SEQ_BIT_SHIFT)
+
+#define PMU_SHA1_GID_SIGNATURE 0xA7C66AD2
+#define PMU_SHA1_GID_SIGNATURE_SIZE 4
+
+#define PMU_SHA1_GID_SIZE 16
+
+struct pmu_queue {
+
+ /* used by hw, for BIOS/SMI queue */
+ u32 mutex_id;
+ u32 mutex_lock;
+ /* used by sw, for LPQ/HPQ queue */
+ struct mutex mutex;
+
+ /* current write position */
+ u32 position;
+ /* physical dmem offset where this queue begins */
+ u32 offset;
+ /* logical queue identifier */
+ u32 id;
+ /* physical queue index */
+ u32 index;
+ /* in bytes */
+ u32 size;
+
+ /* open-flag */
+ u32 oflag;
+ bool opened; /* opened implies locked */
+};
+
+struct pmu_sha1_gid {
+ bool valid;
+ u8 gid[PMU_SHA1_GID_SIZE];
+};
+
+struct pmu_sha1_gid_data {
+ u8 signature[PMU_SHA1_GID_SIGNATURE_SIZE];
+ u8 gid[PMU_SHA1_GID_SIZE];
+};
+
+struct pmu_desc {
+
+ struct pmu_ucode_desc *desc;
+ struct pmu_buf_desc ucode;
+
+ struct pmu_buf_desc pg_buf;
+ /* TBD: remove this if ZBC seq is fixed */
+ struct pmu_buf_desc seq_buf;
+ struct pmu_buf_desc trace_buf;
+ bool buf_loaded;
+
+ struct pmu_sha1_gid gid_info;
+
+ struct pmu_queue queue[PMU_QUEUE_COUNT];
+
+ struct pmu_sequence *seq;
+ unsigned long pmu_seq_tbl[PMU_SEQ_TBL_SIZE];
+ u32 next_seq_desc;
+
+ struct pmu_mutex *mutex;
+ u32 mutex_cnt;
+
+ struct mutex pmu_copy_lock;
+ struct mutex pmu_seq_lock;
+
+ struct nvkm_pmu_allocator dmem;
+
+ u32 *ucode_image;
+ bool pmu_ready;
+
+ u32 zbc_save_done;
+
+ u32 stat_dmem_offset;
+
+ u32 elpg_stat;
+
+ int pmu_state;
+
+#define PMU_ELPG_ENABLE_ALLOW_DELAY_MSEC 1 /* msec */
+ struct work_struct isr_workq;
+ struct mutex elpg_mutex; /* protect elpg enable/disable */
+/* disable -1, enable +1, <=0 elpg disabled, > 0 elpg enabled */
+ int elpg_refcnt;
+
+ bool initialized;
+
+ void (*remove_support)(struct pmu_desc *pmu);
+ bool sw_ready;
+ bool perfmon_ready;
+
+ u32 sample_buffer;
+ u32 load_shadow;
+ u32 load_avg;
+
+ struct mutex isr_mutex;
+ bool isr_enabled;
+
+ bool zbc_ready;
+ unsigned long perfmon_events_cnt;
+ bool perfmon_sampling_enabled;
+ u8 pmu_mode;
+ u32 falcon_id;
+ u32 aelpg_param[5];
+ void *pmu_chip_data;
+ struct nvkm_pmu *pmu;
+};
struct nvkm_pmu_impl {
struct nvkm_oclass base;
@@ -39,5 +296,12 @@ struct nvkm_pmu_impl {
} data;
void (*pgob)(struct nvkm_pmu *, bool);
+ struct pmu_desc pmudata;
};
+
+static inline struct nvkm_pmu *impl_from_pmu(struct pmu_desc *pmu)
+{
+ return pmu->pmu;
+}
+
#endif
--
1.9.1
More information about the Nouveau
mailing list