diff mbox

[V2] pmu/gk20a: PMU boot support.

Message ID 1428468144-13988-1-git-send-email-dgoyal@nvidia.com
State Deferred
Headers show

Commit Message

Deepak Goyal April 8, 2015, 4:42 a.m. UTC
- Maps PMU firmware into PMU virtual memory.
- Copy bootloader into PMU memory and start it.
- Allow the PMU to interact with HOST via interrupts.

PMU after successful configurations (to follow after this patch) will:
1.Autonomously power gate graphics engine when not in use.It will save
us a lot of power.
2.Provide better way to scale frequencies by reporting Perf counters.
3.Be critical for GPU functionality as future GPUs secure some register
& mem accesses involved in context switch.

Signed-off-by: Deepak Goyal <dgoyal@nvidia.com>
---
 drm/nouveau/nvkm/subdev/pmu/gk20a.c | 855 +++++++++++++++++++++++++++++++++++-
 1 file changed, 840 insertions(+), 15 deletions(-)

Comments

Alexandre Courbot April 9, 2015, 7:45 a.m. UTC | #1
Note: we did a few internal review rounds with Deepak to trim this
patch as much as possible. I hope it is now in shape for public review
again.

By itself this patch adds little feature beyond loading the PMU
firmware and acknowledging its start, but secure boot and power-saving
features should follow shortly. We wanted to start with something not
too big for Deepak to cut his teeth on.

One of the questions here is whether (and how) this can be
reused/factorized with some of the code in base.c. This patch adds
support for NVIDIA's PMU firmware, while the existing Nouveau code
interacts with the Nouveau firmware. At first sight, the way messages
are sent/received seem to be different enough to justify a different
code. But some of the infrastructure (like the message receive
work_struct) are duplicated. We also avoid calling nvkm_pmu_init,
which is not elegant.

Support for NVIDIA PMU firmware becomes absolutely necessary, since as
you know Maxwell+ cards require secure firmware loading to operate,
and secure firmware can only be loaded by the PMU.

Ben, do you think we could add some abstraction in pmu/base.c to
better handle the option of using Nouveau's or NVIDIA's PMU firmware?
If you agree, I can probably come with a patch quickly.

Review for Deepak follows...

On Wed, Apr 8, 2015 at 1:42 PM, Deepak Goyal <dgoyal@nvidia.com> wrote:
> - Maps PMU firmware into PMU virtual memory.
> - Copy bootloader into PMU memory and start it.
> - Allow the PMU to interact with HOST via interrupts.
>
> PMU after successful configurations (to follow after this patch) will:
> 1.Autonomously power gate graphics engine when not in use.It will save
> us a lot of power.
> 2.Provide better way to scale frequencies by reporting Perf counters.
> 3.Be critical for GPU functionality as future GPUs secure some register
> & mem accesses involved in context switch.
>
> Signed-off-by: Deepak Goyal <dgoyal@nvidia.com>
> ---
>  drm/nouveau/nvkm/subdev/pmu/gk20a.c | 855 +++++++++++++++++++++++++++++++++++-
>  1 file changed, 840 insertions(+), 15 deletions(-)
>
> diff --git a/drm/nouveau/nvkm/subdev/pmu/gk20a.c b/drm/nouveau/nvkm/subdev/pmu/gk20a.c
> index 594f746e68f2..328c0a68d615 100644
> --- a/drm/nouveau/nvkm/subdev/pmu/gk20a.c
> +++ b/drm/nouveau/nvkm/subdev/pmu/gk20a.c
> @@ -1,5 +1,5 @@
>  /*
> - * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
> + * Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved.

Can you make this 2014-2015? You haven't removed any code from 2014. :P

>   *
>   * Permission is hereby granted, free of charge, to any person obtaining a
>   * copy of this software and associated documentation files (the "Software"),
> @@ -20,13 +20,180 @@
>   * DEALINGS IN THE SOFTWARE.
>   */
>  #include "priv.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 <core/object.h>
> +#include <core/device.h>
> +#include <linux/delay.h>       /* for mdelay */

This comment looks lonely, you can probably remove it...

> +#include <linux/firmware.h>
>  #include <subdev/clk.h>
>  #include <subdev/timer.h>
>  #include <subdev/volt.h>
>
> +#define APP_VERSION_GK20A  17997577
> +#define GK20A_PMU_UCODE_SIZE_MAX  (256 * 1024)
> +#define PMU_QUEUE_COUNT  5
> +
> +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_buf_desc {
> +       struct nvkm_gpuobj *obj;
> +       struct nvkm_vma vma;
> +       size_t size;
> +};
> +
> +struct nvkm_pmu_priv_vm {
> +       struct nvkm_gpuobj *mem;
> +       struct nvkm_gpuobj *pgd;
> +       struct nvkm_vm *vm;
> +};
> +
> +/* Choices for pmu_state */
> +enum {
> +       PMU_STATE_OFF,             /*0  PMU is off */
> +       PMU_STATE_STARTING,        /*1  PMU is on, but not booted */
> +       PMU_STATE_INIT_RECEIVED    /*2  PMU init message received */
> +};
> +
> +struct pmu_mem_gk20a {
> +       u32 dma_base;
> +       u8  dma_offset;
> +       u8  dma_idx;
> +       u16 fb_size;
> +};
> +
> +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;
> +       u32 app_resident_code_size;
> +       u32 app_resident_data_offset;
> +       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_ALIGNMENT             (4)
> +
> +struct pmu_hdr {
> +       u8 unit_id;
> +       u8 size;
> +       u8 ctrl_flags;
> +       u8 seq_id;
> +};
> +
> +#define PMU_MSG_HDR_SIZE       sizeof(struct pmu_hdr)
> +
> +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;
> +};
> +
> +struct pmu_msg {
> +       struct pmu_hdr hdr;
> +       union {
> +               struct pmu_init_msg init;
> +               struct pmu_rc_msg rc;
> +       } msg;
> +};
> +
>  #define BUSY_SLOT      0
>  #define CLK_SLOT       7
> +#define GK20A_PMU_UCODE_IMAGE  "gpmu_ucode.bin"
>
>  struct gk20a_pmu_dvfs_data {
>         int p_load_target;
> @@ -39,8 +206,22 @@ struct gk20a_pmu_priv {
>         struct nvkm_pmu base;
>         struct nvkm_alarm alarm;
>         struct gk20a_pmu_dvfs_data *data;
> +       struct pmu_ucode_desc *desc;
> +       struct pmu_buf_desc ucode;
> +       struct pmu_buf_desc trace_buf;
> +       struct mutex pmu_copy_lock;
> +       bool pmu_ready;
> +       int pmu_state;
> +       struct nvkm_pmu_priv_vm pmuvm;
> +       struct work_struct isr_workq;
> +       bool initialized;
> +       bool sw_ready;
> +       struct mutex isr_mutex;
> +       bool isr_enabled;
>  };
>
> +#define to_gk20a_priv(ptr) container_of(ptr, struct gk20a_pmu_priv, base)
> +
>  struct gk20a_pmu_dvfs_dev_status {
>         unsigned long total;
>         unsigned long busy;
> @@ -48,6 +229,61 @@ struct gk20a_pmu_dvfs_dev_status {
>  };
>
>  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_release_firmware(struct nvkm_pmu *ppmu, const struct firmware *pfw)
> +{
> +       nv_debug(ppmu, "firmware released\n");
> +       release_firmware(pfw);
> +}
> +
> +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
>  gk20a_pmu_dvfs_target(struct gk20a_pmu_priv *priv, int *state)
>  {
>         struct nvkm_clk *clk = nvkm_clk(priv);
> @@ -163,32 +399,601 @@ static int
>  gk20a_pmu_fini(struct nvkm_object *object, bool suspend)
>  {
>         struct nvkm_pmu *pmu = (void *)object;
> -       struct gk20a_pmu_priv *priv = (void *)pmu;
> -
> +       struct gk20a_pmu_priv *priv = to_gk20a_priv(pmu);
> +       nv_wr32(pmu, 0x10a014, 0x00000060);
> +       flush_work(&pmu->recv.work);

This is normally done by _nvkm_pmu_fini, but since we did not call
nvkm_pmu_init I understand you are reluctant to use it here...

In any case, it is impossible for pmu->recv.work to have been
triggered since we are using our own interrupt handler. So you don't
need to bother with this code here.

>         nvkm_timer_alarm_cancel(priv, &priv->alarm);
>
>         return nvkm_subdev_fini(&pmu->base, suspend);
>  }
>
>  static int
> +gk20a_pmu_enable_hw(struct nvkm_pmu *ppmu, struct nvkm_mc *pmc,
> +                       bool enable)
> +{
> +       if (enable) {
> +               nv_mask(pmc, 0x000200, 0x00002000, 0x00002000);
> +               nv_rd32(pmc, 0x00000200);
> +               if (nv_wait(ppmu, 0x0010a10c, 0x00000006, 0x00000000))
> +                       return 0;
> +               nv_mask(pmc, 0x00000200, 0x2000, 0x00000000);
> +               nv_error(ppmu, "Falcon mem scrubbing timeout\n");
> +               return -ETIMEDOUT;
> +       } else {
> +               nv_mask(pmc, 0x00000200, 0x2000, 0x00000000);
> +               return 0;
> +       }
> +}
> +static void
> +pmu_enable_irq(struct nvkm_pmu *ppmu, struct nvkm_mc *pmc, bool enable)

This function and the few next ones are missing a gk20a_ prefix. It is
not critical since they are static, but let's try to avoid name
collision anyway...

> +{
> +       if (enable) {
> +               nv_debug(ppmu, "enable pmu irq\n");
> +               nv_wr32(ppmu, 0x0010a010, 0xff);
> +               nv_mask(pmc, 0x00000640, 0x1000000, 0x1000000);
> +               nv_mask(pmc, 0x00000644, 0x1000000, 0x1000000);
> +       } else {
> +               nv_debug(ppmu, "disable pmu irq\n");
> +               nv_mask(pmc, 0x00000640, 0x1000000, 0x00000000);
> +               nv_mask(pmc, 0x00000644, 0x1000000, 0x00000000);
> +               nv_wr32(ppmu, 0x0010a014, 0xff);
> +       }
> +
> +}
> +
> +static int
> +pmu_idle(struct nvkm_pmu *ppmu)
> +{
> +       if (!nv_wait(ppmu, 0x0010a04c, 0x0000ffff, 0x00000000)) {
> +               nv_error(ppmu, "timedout waiting pmu idle\n");
> +               return -EBUSY;
> +       }
> +
> +       return 0;
> +}
> +
> +static int
> +pmu_enable(struct nvkm_pmu *ppmu, struct nvkm_mc *pmc, bool enable)
> +{
> +       u32 pmc_enable;
> +       int err;
> +
> +       if (enable) {
> +               err = gk20a_pmu_enable_hw(ppmu, pmc, true);
> +               if (err)
> +                       return err;
> +
> +               err = pmu_idle(ppmu);
> +               if (err)
> +                       return err;
> +
> +               pmu_enable_irq(ppmu, pmc, true);
> +       } else {
> +               pmc_enable = nv_rd32(pmc, 0x200);
> +               if ((pmc_enable & 0x2000) != 0x0) {
> +                       pmu_enable_irq(ppmu, pmc, false);
> +                       gk20a_pmu_enable_hw(ppmu, pmc, false);
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +int
> +pmu_reset(struct nvkm_pmu *ppmu, struct nvkm_mc *pmc)

This function should probably be static.

> +{
> +       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 void
> +pmu_copy_to_dmem(struct gk20a_pmu_priv *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 = &pmu->base;
> +
> +       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 = 0xfffc;
> +
> +       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 void
> +pmu_copy_from_dmem(struct gk20a_pmu_priv *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 = &pmu->base;
> +
> +       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 = 0xfffc;
> +
> +       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__);
> +}
> +
> +static int
> +pmu_process_init_msg(struct gk20a_pmu_priv *pmu, struct pmu_msg *msg)
> +{
> +       struct nvkm_pmu *ppmu = &pmu->base;
> +       struct pmu_init_msg_pmu_gk20a *init;
> +       u32 tail;
> +
> +       tail = nv_rd32(ppmu, 0x0010a4cc);
> +
> +       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");

On several occasions in this patch you have unexpected line cuts like
this one. Can you fix them? If a statement can fit in a single line,
it should be on a single line.

> +               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);
> +       init = &msg->msg.init.pmu_init_gk20a;

This variable is set, but never used afterwards.

> +       pmu->pmu_ready = true;
> +       pmu->pmu_state = PMU_STATE_INIT_RECEIVED;
> +       nv_debug(ppmu, "init msg processed\n");
> +       return 0;
> +}
> +
> +static void
> +gk20a_pmu_process_message(struct work_struct *work)
> +{
> +       struct gk20a_pmu_priv *pmu = container_of(work,
> +                                             struct gk20a_pmu_priv, isr_workq);
> +       struct pmu_msg msg;
> +       struct nvkm_pmu *ppmu = &pmu->base;
> +       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);

Why do you need this pmu_enable_irq here? gk20a_pmu_intr enables them
before exiting, so this seems to be unneeded. Or do you want to keep
interrupts disabled until the message is processed? In that case you
need to revise gk20a_pmu_intr.

> +       } else
> +               mutex_unlock(&pmu->isr_mutex);
> +}
> +
> +static int
> +gk20a_pmu_init_vm(struct nvkm_pmu *ppmu, const struct firmware *fw)
> +{
> +       int ret = 0;
> +       struct gk20a_pmu_priv *pmu = to_gk20a_priv(ppmu);
> +       u32 *ucode_image;
> +       struct pmu_ucode_desc *desc = (struct pmu_ucode_desc *)fw->data;
> +       int i;
> +       struct nvkm_pmu_priv_vm *ppmuvm = &pmu->pmuvm;
> +       struct nvkm_device *device = nv_device(&ppmu->base);
> +       struct nvkm_vm *vm;
> +       const u64 pmu_area_len = 300*1024;
> +       /* 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 fw_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 fw_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.obj);
> +       if (ret)
> +               goto fw_alloc_err;

If you meet an error here you also need to unreference vm to allow it
to be deleted, using nvkm_vm_ref(NULL, &ppuvm->vm, ppmuvm->pgd)

> +
> +       ucode_image = (u32 *)((u8 *)desc + desc->descriptor_size);
> +       for (i = 0; i < (desc->app_start_offset + desc->app_size); i += 4)
> +               nv_wo32(pmu->ucode.obj, i, ucode_image[i/4]);
> +
> +       /* map allocated memory into GMMU */
> +       ret = nvkm_gpuobj_map_vm(nv_gpuobj(pmu->ucode.obj), vm,
> +                                   NV_MEM_ACCESS_RW, &pmu->ucode.vma);
> +       if (ret)
> +               goto map_err;
> +
> +       nv_debug(ppmu, "%s function end\n", __func__);
> +       return ret;
> +map_err:
> +       nvkm_gpuobj_destroy(pmu->ucode.obj);
> +fw_alloc_err:
> +       nvkm_gpuobj_destroy(ppmuvm->pgd);
> +pgd_alloc_err:
> +       nvkm_gpuobj_destroy(ppmuvm->mem);

Use nvkm_gpuobj_ref(NULL, &ptr) to delete GPU objects to follow the
conventions established by Nouveau.

> +instblk_alloc_err:
> +       return ret;
> +}

So in this function you have allocated plenty of nice GPU objects, but
they are never deleted. Your fini function should call nvkm_gpuobj_ref
and nvkm_vm_ref in order to remove them properly.

And actually, thanks to Nouveau's object-oriented nature, I don't even
think you need these error-handling labels at all. If your init
function fails, the fini function will be called. So all you need to
do is to have this in your fini function (probably only if !suspend):

    nvkm_gpuobj_unmap(&priv->ucode.vma);
    nvkm_gpuobj_ref(NULL, &priv->ucode.obj);
    nvkm_vm_ref(NULL, &priv->pmuvm.vm, priv->pmuvm.pgd);
    nvkm_gpuobj_ref(NULL, &priv->pmuvm.pgd);
    nvkm_gpuobj_ref(NULL, &priv->pmuvm.mem);

... and the right thing should happen. Don't worry about objects not
yet initialized, Nouveau can handle them gracefully.

> +
> +static int
> +gk20a_init_pmu_setup_sw(struct nvkm_pmu *ppmu)
> +{
> +       struct gk20a_pmu_priv *pmu = to_gk20a_priv(ppmu);
> +       struct nvkm_pmu_priv_vm *ppmuvm = &pmu->pmuvm;
> +       int err = 0;
> +       int ret = 0;
> +
> +       if (pmu->sw_ready) {
> +               nv_debug(ppmu, "skipping init\n");
> +               goto skip_init;
> +       }
> +
> +       /* no infoRom script from vbios? */
> +
> +       /* TBD: sysmon subtask */
> +
> +       INIT_WORK(&pmu->isr_workq, gk20a_pmu_process_message);

Here it would be nice to use pmu->base.recv.work, but we will need to
abstract things from base.c...

> +
> +       ret = nvkm_gpuobj_new(nv_object(ppmu), NULL, GK20A_PMU_TRACE_BUFSIZE,
> +                                           0, 0, &pmu->trace_buf.obj);

Same remark as above for this object which must be deleted in the fini function.

> +       if (ret)
> +               goto err;
> +       ret = nvkm_gpuobj_map_vm(nv_gpuobj(pmu->trace_buf.obj),
> +                                       ppmuvm->vm,
> +                                       NV_MEM_ACCESS_RW,
> +                                       &pmu->trace_buf.vma);
> +       if (ret)
> +               goto map_err;
> +
> +       pmu->sw_ready = true;
> +
> +skip_init:
> +       return 0;
> +map_err:
> +       nvkm_gpuobj_destroy(pmu->trace_buf.obj);

And same remark that these labels are not needed. In your fini
function, just have this:

    nvkm_gpuobj_unmap(&priv->trace_buf.vma);
    nvkm_gpuobj_ref(NULL, &priv->trace_buf.obj);

> +err:
> +       return err;
> +}
> +
> +static int
> +pmu_bootstrap(struct gk20a_pmu_priv *pmu)

gk20a_pmu_bootstrap.

> +{
> +       struct nvkm_pmu *ppmu = &pmu->base;
> +       struct pmu_ucode_desc *desc = pmu->desc;
> +       u32 addr_code, addr_data, addr_load;
> +       u32 i, blocks, addr_args;
> +       struct pmu_cmdline_args_gk20a cmdline_args;
> +       struct nvkm_pmu_priv_vm *ppmuvm = &pmu->pmuvm;
> +       nv_mask(ppmu, 0x0010a048, 0x01, 0x01);

Empty line between declarations and code please.

> +       /*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 =
> +                           lower_32_bits(pmu->trace_buf.vma.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 = lower_32_bits((pmu->ucode.vma.offset +
> +                       desc->app_start_offset +
> +                       desc->app_resident_code_offset) >> 8);
> +
> +       addr_data = lower_32_bits((pmu->ucode.vma.offset +
> +                       desc->app_start_offset +
> +                       desc->app_resident_data_offset) >> 8);
> +
> +       addr_load = lower_32_bits((pmu->ucode.vma.offset +
> +                       desc->bootloader_start_offset) >> 8);
> +
> +       nv_wr32(ppmu, 0x0010a1c4, GK20A_PMU_DMAIDX_UCODE);
> +       nv_debug(ppmu, "0x%08x\n", GK20A_PMU_DMAIDX_UCODE);
> +       nv_wr32(ppmu, 0x0010a1c4, (addr_code));
> +       nv_debug(ppmu, "0x%08x\n", (addr_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,  (addr_data));
> +       nv_debug(ppmu, "0x%08x\n", (addr_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, (addr_code));
> +       nv_debug(ppmu, "0x%08x\n", (addr_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,
> +               (addr_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));

Since your last argument is a u32, that mask seems to be pointless.
Also arguments fit on one line, so only use one line please.

> +
> +       nv_wr32(ppmu, 0x0010a100, 0x1 << 1);
> +
> +       nv_wr32(ppmu, 0x0010a080, desc->app_version);
> +
> +       return 0;
> +}
> +
> +static int
> +gk20a_init_pmu_setup_hw1(struct nvkm_pmu *ppmu, struct nvkm_mc *pmc)
> +{
> +       struct gk20a_pmu_priv *pmu = to_gk20a_priv(ppmu);
> +       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 void
> +gk20a_pmu_intr(struct nvkm_subdev *subdev)
> +{
> +       struct nvkm_pmu *ppmu = nvkm_pmu(subdev);
> +       struct gk20a_pmu_priv *pmu = to_gk20a_priv(ppmu);
> +       struct nvkm_mc *pmc = nvkm_mc(ppmu);
> +       u32 intr, mask;
> +       if (!pmu->isr_enabled)
> +               return;
> +
> +       mask = nv_rd32(ppmu, 0x0010a018) & nv_rd32(ppmu, 0x0010a01c);
> +
> +       intr = nv_rd32(ppmu, 0x0010a008) & mask;
> +
> +       nv_error(ppmu, "received falcon interrupt: 0x%08x", intr);

Does this message need to be a nv_error?

> +       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");

Aren't you missing a "goto out" here?

Well to be honest these "goto out" are not very useful at the moment.
Do you plan to add code later that will make them meaningful? If not,
better to take them out.

> +
> +       if (intr & 0x20) {
> +               nv_error(ppmu,
> +                       "pmu exterr intr not implemented. Clearing interrupt.");
> +               nv_mask(ppmu, 0x0010a16c, (0x1 << 31), 0x00000000);
> +               goto out;
> +       }
> +       if (intr & 0x40) {
> +               nv_debug(ppmu, "scheduling work\n");
> +               schedule_work(&pmu->isr_workq);
> +               goto out;
> +       }
> +
> +out:
> +       pmu_enable_irq(ppmu, pmc, true);
> +       nv_wr32(ppmu, 0x0010a004, intr);
> +       nv_debug(ppmu, "irq handled\n");
> +}
> +
> +static void
> +gk20a_pmu_pgob(struct nvkm_pmu *ppmu, bool enable)
> +{
> +}
> +
> +static 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 gk20a_pmu_priv *pmu = to_gk20a_priv(ppmu);
> +       const struct firmware *pmufw = NULL;
>         int ret;
>
> -       ret = nvkm_subdev_init(&pmu->base);
> +       nv_subdev(ppmu)->intr = gk20a_pmu_intr;
> +
> +       mutex_init(&pmu->isr_mutex);
> +       mutex_init(&pmu->pmu_copy_lock);
> +
> +       if (pmufw == NULL) {

This statement will always be true.

> +               ret = gk20a_pmu_load_firmware(ppmu, &pmufw);
> +               if (ret < 0) {
> +                       nv_error(ppmu, "failed to load pmu fimware\n");
> +                       return ret;
> +               }
> +               ret = gk20a_pmu_init_vm(ppmu, pmufw);
> +               if (ret < 0) {
> +                       nv_error(ppmu, "failed to map pmu fw to va space\n");
> +                       goto 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 err;
> +       }
> +
> +       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;
>
> -       pmu->pgob = nvkm_pmu_pgob;
> +       ret = nvkm_subdev_init(&ppmu->base);
> +       if (ret)
> +               goto err;

The call the nvkm_subdev_init should be the very first thing you do in
this function.

>
> -       /* 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);
> +       ppmu->pgob = nvkm_pmu_pgob;
>
> -       nvkm_timer_alarm(pmu, 2000000000, &priv->alarm);
> +       pmu->initialized = true;
> +       /* 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(ppmu, 2000000000, &pmu->alarm);
> +err:
> +       gk20a_pmu_release_firmware(ppmu, pmufw);
>         return ret;
>  }
>
> @@ -218,13 +1023,33 @@ gk20a_pmu_ctor(struct nvkm_object *parent, struct nvkm_object *engine,
>         return 0;
>  }
>
> +static void
> +gk20a_pmu_dtor(struct nvkm_object *object)
> +{

Your destructor should be the mirror function of the constructor.
However this function undoes things that are done by the init function
(and therefore should be undone in your _fini function).

> +       struct nvkm_pmu *ppmu = (void *)object;
> +       struct nvkm_mc *pmc = nvkm_mc(object);
> +       struct gk20a_pmu_priv *pmu = to_gk20a_priv(ppmu);
> +
> +       /* make sure the pending operations are finished before we continue */
> +       cancel_work_sync(&pmu->isr_workq);

This for instance should probably be moved to gk20_pmu_fini.

> +       pmu->initialized = false;

... and this one too, obviously.

> +
> +       mutex_lock(&pmu->isr_mutex);
> +       pmu_enable(ppmu, pmc, false);
> +       pmu->isr_enabled = false;
> +       mutex_unlock(&pmu->isr_mutex);

Same here, you enable the PMU in the init function, logically you will
disable it in fini.

You have a guarantee that the fini function will be called before the
destructor, so everything will be fine that way.

> +       pmu->pmu_state = PMU_STATE_OFF;
> +       pmu->pmu_ready = false;

... and all the same for these.

If you look at gk20a_pmu_ctor, you can deduct that the only things you
would need to do in gk20a_pmu_dtor are un-initializing the alarm
(which doesn't require any operation), and calling _nvkm_pmu_dtor. So
after moving these statements to the right place, your destructor
should just be a call to _nvkm_pmu_dtor(), which means that you can
remove it altogether...

> +}
> +
>  struct nvkm_oclass *
>  gk20a_pmu_oclass = &(struct nvkm_pmu_impl) {
>         .base.handle = NV_SUBDEV(PMU, 0xea),
>         .base.ofuncs = &(struct nvkm_ofuncs) {
>                 .ctor = gk20a_pmu_ctor,
> -               .dtor = _nvkm_pmu_dtor,
> +               .dtor = gk20a_pmu_dtor,

... and let _nvkm_pmu_dtor be called here as it was before.

>                 .init = gk20a_pmu_init,
>                 .fini = gk20a_pmu_fini,
>         },
> +       .pgob = gk20a_pmu_pgob,
>  }.base;
> --
> 1.9.1
>

Urgh, I realize there is still a lot of stuff to be done before this
can be merged. I will let you address the obvious code fixes, but will
try to understand with Ben how this should integrate the existing PMU
support. If Ben agrees with my proposal for an abstraction, I will do
the core rework and then your code will fit with only minor changes.

Good luck for the public v3! ;)
--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Alexandre Courbot April 9, 2015, 8:34 a.m. UTC | #2
Also, with this version it seems like I am not receiving any
interrupt, and thus that the PMU initialization does not complete.
Previous versions worked fine though, so you may want to check that.
--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Alexandre Courbot April 10, 2015, 7:10 a.m. UTC | #3
>> +
>> +       ucode_image = (u32 *)((u8 *)desc + desc->descriptor_size);
>> +       for (i = 0; i < (desc->app_start_offset + desc->app_size); i += 4)
>> +               nv_wo32(pmu->ucode.obj, i, ucode_image[i/4]);
>> +
>> +       /* map allocated memory into GMMU */
>> +       ret = nvkm_gpuobj_map_vm(nv_gpuobj(pmu->ucode.obj), vm,
>> +                                   NV_MEM_ACCESS_RW, &pmu->ucode.vma);
>> +       if (ret)
>> +               goto map_err;
>> +
>> +       nv_debug(ppmu, "%s function end\n", __func__);
>> +       return ret;
>> +map_err:
>> +       nvkm_gpuobj_destroy(pmu->ucode.obj);
>> +fw_alloc_err:
>> +       nvkm_gpuobj_destroy(ppmuvm->pgd);
>> +pgd_alloc_err:
>> +       nvkm_gpuobj_destroy(ppmuvm->mem);
>
> Use nvkm_gpuobj_ref(NULL, &ptr) to delete GPU objects to follow the
> conventions established by Nouveau.
>
>> +instblk_alloc_err:
>> +       return ret;
>> +}
>
> So in this function you have allocated plenty of nice GPU objects, but
> they are never deleted. Your fini function should call nvkm_gpuobj_ref
> and nvkm_vm_ref in order to remove them properly.
>
> And actually, thanks to Nouveau's object-oriented nature, I don't even
> think you need these error-handling labels at all. If your init
> function fails, the fini function will be called. So all you need to
> do is to have this in your fini function (probably only if !suspend):

Oops, what I said here is simply not true. Resource allocation needs
to be moved to the ctor function for the desired behavior to occur. It
should be done there to begin with anyway.

Deepak, keep in mind that in that case your dtor function can be
called on an incomplete object, so be sure to take that into account.
--
To unsubscribe from this list: send the line "unsubscribe linux-tegra" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drm/nouveau/nvkm/subdev/pmu/gk20a.c b/drm/nouveau/nvkm/subdev/pmu/gk20a.c
index 594f746e68f2..328c0a68d615 100644
--- a/drm/nouveau/nvkm/subdev/pmu/gk20a.c
+++ b/drm/nouveau/nvkm/subdev/pmu/gk20a.c
@@ -1,5 +1,5 @@ 
 /*
- * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
+ * 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"),
@@ -20,13 +20,180 @@ 
  * DEALINGS IN THE SOFTWARE.
  */
 #include "priv.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 <core/object.h>
+#include <core/device.h>
+#include <linux/delay.h>	/* for mdelay */
+#include <linux/firmware.h>
 #include <subdev/clk.h>
 #include <subdev/timer.h>
 #include <subdev/volt.h>
 
+#define APP_VERSION_GK20A  17997577
+#define GK20A_PMU_UCODE_SIZE_MAX  (256 * 1024)
+#define PMU_QUEUE_COUNT  5
+
+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_buf_desc {
+	struct nvkm_gpuobj *obj;
+	struct nvkm_vma vma;
+	size_t size;
+};
+
+struct nvkm_pmu_priv_vm {
+	struct nvkm_gpuobj *mem;
+	struct nvkm_gpuobj *pgd;
+	struct nvkm_vm *vm;
+};
+
+/* Choices for pmu_state */
+enum {
+	PMU_STATE_OFF,             /*0  PMU is off */
+	PMU_STATE_STARTING,        /*1  PMU is on, but not booted */
+	PMU_STATE_INIT_RECEIVED    /*2  PMU init message received */
+};
+
+struct pmu_mem_gk20a {
+	u32 dma_base;
+	u8  dma_offset;
+	u8  dma_idx;
+	u16 fb_size;
+};
+
+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;
+	u32 app_resident_code_size;
+	u32 app_resident_data_offset;
+	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_ALIGNMENT		(4)
+
+struct pmu_hdr {
+	u8 unit_id;
+	u8 size;
+	u8 ctrl_flags;
+	u8 seq_id;
+};
+
+#define PMU_MSG_HDR_SIZE	sizeof(struct pmu_hdr)
+
+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;
+};
+
+struct pmu_msg {
+	struct pmu_hdr hdr;
+	union {
+		struct pmu_init_msg init;
+		struct pmu_rc_msg rc;
+	} msg;
+};
+
 #define BUSY_SLOT	0
 #define CLK_SLOT	7
+#define GK20A_PMU_UCODE_IMAGE	"gpmu_ucode.bin"
 
 struct gk20a_pmu_dvfs_data {
 	int p_load_target;
@@ -39,8 +206,22 @@  struct gk20a_pmu_priv {
 	struct nvkm_pmu base;
 	struct nvkm_alarm alarm;
 	struct gk20a_pmu_dvfs_data *data;
+	struct pmu_ucode_desc *desc;
+	struct pmu_buf_desc ucode;
+	struct pmu_buf_desc trace_buf;
+	struct mutex pmu_copy_lock;
+	bool pmu_ready;
+	int pmu_state;
+	struct nvkm_pmu_priv_vm pmuvm;
+	struct work_struct isr_workq;
+	bool initialized;
+	bool sw_ready;
+	struct mutex isr_mutex;
+	bool isr_enabled;
 };
 
+#define to_gk20a_priv(ptr) container_of(ptr, struct gk20a_pmu_priv, base)
+
 struct gk20a_pmu_dvfs_dev_status {
 	unsigned long total;
 	unsigned long busy;
@@ -48,6 +229,61 @@  struct gk20a_pmu_dvfs_dev_status {
 };
 
 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_release_firmware(struct nvkm_pmu *ppmu, const struct firmware *pfw)
+{
+	nv_debug(ppmu, "firmware released\n");
+	release_firmware(pfw);
+}
+
+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
 gk20a_pmu_dvfs_target(struct gk20a_pmu_priv *priv, int *state)
 {
 	struct nvkm_clk *clk = nvkm_clk(priv);
@@ -163,32 +399,601 @@  static int
 gk20a_pmu_fini(struct nvkm_object *object, bool suspend)
 {
 	struct nvkm_pmu *pmu = (void *)object;
-	struct gk20a_pmu_priv *priv = (void *)pmu;
-
+	struct gk20a_pmu_priv *priv = to_gk20a_priv(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 int
+gk20a_pmu_enable_hw(struct nvkm_pmu *ppmu, struct nvkm_mc *pmc,
+			bool enable)
+{
+	if (enable) {
+		nv_mask(pmc, 0x000200, 0x00002000, 0x00002000);
+		nv_rd32(pmc, 0x00000200);
+		if (nv_wait(ppmu, 0x0010a10c, 0x00000006, 0x00000000))
+			return 0;
+		nv_mask(pmc, 0x00000200, 0x2000, 0x00000000);
+		nv_error(ppmu, "Falcon mem scrubbing timeout\n");
+		return -ETIMEDOUT;
+	} else {
+		nv_mask(pmc, 0x00000200, 0x2000, 0x00000000);
+		return 0;
+	}
+}
+static void
+pmu_enable_irq(struct nvkm_pmu *ppmu, struct nvkm_mc *pmc, bool enable)
+{
+	if (enable) {
+		nv_debug(ppmu, "enable pmu irq\n");
+		nv_wr32(ppmu, 0x0010a010, 0xff);
+		nv_mask(pmc, 0x00000640, 0x1000000, 0x1000000);
+		nv_mask(pmc, 0x00000644, 0x1000000, 0x1000000);
+	} else {
+		nv_debug(ppmu, "disable pmu irq\n");
+		nv_mask(pmc, 0x00000640, 0x1000000, 0x00000000);
+		nv_mask(pmc, 0x00000644, 0x1000000, 0x00000000);
+		nv_wr32(ppmu, 0x0010a014, 0xff);
+	}
+
+}
+
+static int
+pmu_idle(struct nvkm_pmu *ppmu)
+{
+	if (!nv_wait(ppmu, 0x0010a04c, 0x0000ffff, 0x00000000)) {
+		nv_error(ppmu, "timedout waiting pmu idle\n");
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+static int
+pmu_enable(struct nvkm_pmu *ppmu, struct nvkm_mc *pmc, bool enable)
+{
+	u32 pmc_enable;
+	int err;
+
+	if (enable) {
+		err = gk20a_pmu_enable_hw(ppmu, pmc, true);
+		if (err)
+			return err;
+
+		err = pmu_idle(ppmu);
+		if (err)
+			return err;
+
+		pmu_enable_irq(ppmu, pmc, true);
+	} else {
+		pmc_enable = nv_rd32(pmc, 0x200);
+		if ((pmc_enable & 0x2000) != 0x0) {
+			pmu_enable_irq(ppmu, pmc, false);
+			gk20a_pmu_enable_hw(ppmu, pmc, false);
+		}
+	}
+
+	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 void
+pmu_copy_to_dmem(struct gk20a_pmu_priv *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 = &pmu->base;
+
+	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 = 0xfffc;
+
+	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 void
+pmu_copy_from_dmem(struct gk20a_pmu_priv *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 = &pmu->base;
+
+	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 = 0xfffc;
+
+	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__);
+}
+
+static int
+pmu_process_init_msg(struct gk20a_pmu_priv *pmu, struct pmu_msg *msg)
+{
+	struct nvkm_pmu *ppmu = &pmu->base;
+	struct pmu_init_msg_pmu_gk20a *init;
+	u32 tail;
+
+	tail = nv_rd32(ppmu, 0x0010a4cc);
+
+	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);
+	init = &msg->msg.init.pmu_init_gk20a;
+	pmu->pmu_ready = true;
+	pmu->pmu_state = PMU_STATE_INIT_RECEIVED;
+	nv_debug(ppmu, "init msg processed\n");
+	return 0;
+}
+
+static void
+gk20a_pmu_process_message(struct work_struct *work)
+{
+	struct gk20a_pmu_priv *pmu = container_of(work,
+					      struct gk20a_pmu_priv, isr_workq);
+	struct pmu_msg msg;
+	struct nvkm_pmu *ppmu = &pmu->base;
+	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);
+	} else
+		mutex_unlock(&pmu->isr_mutex);
+}
+
+static int
+gk20a_pmu_init_vm(struct nvkm_pmu *ppmu, const struct firmware *fw)
+{
+	int ret = 0;
+	struct gk20a_pmu_priv *pmu = to_gk20a_priv(ppmu);
+	u32 *ucode_image;
+	struct pmu_ucode_desc *desc = (struct pmu_ucode_desc *)fw->data;
+	int i;
+	struct nvkm_pmu_priv_vm *ppmuvm = &pmu->pmuvm;
+	struct nvkm_device *device = nv_device(&ppmu->base);
+	struct nvkm_vm *vm;
+	const u64 pmu_area_len = 300*1024;
+	/* 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 fw_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 fw_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.obj);
+	if (ret)
+		goto fw_alloc_err;
+
+	ucode_image = (u32 *)((u8 *)desc + desc->descriptor_size);
+	for (i = 0; i < (desc->app_start_offset + desc->app_size); i += 4)
+		nv_wo32(pmu->ucode.obj, i, ucode_image[i/4]);
+
+	/* map allocated memory into GMMU */
+	ret = nvkm_gpuobj_map_vm(nv_gpuobj(pmu->ucode.obj), vm,
+				    NV_MEM_ACCESS_RW, &pmu->ucode.vma);
+	if (ret)
+		goto map_err;
+
+	nv_debug(ppmu, "%s function end\n", __func__);
+	return ret;
+map_err:
+	nvkm_gpuobj_destroy(pmu->ucode.obj);
+fw_alloc_err:
+	nvkm_gpuobj_destroy(ppmuvm->pgd);
+pgd_alloc_err:
+	nvkm_gpuobj_destroy(ppmuvm->mem);
+instblk_alloc_err:
+	return ret;
+}
+
+static int
+gk20a_init_pmu_setup_sw(struct nvkm_pmu *ppmu)
+{
+	struct gk20a_pmu_priv *pmu = to_gk20a_priv(ppmu);
+	struct nvkm_pmu_priv_vm *ppmuvm = &pmu->pmuvm;
+	int err = 0;
+	int ret = 0;
+
+	if (pmu->sw_ready) {
+		nv_debug(ppmu, "skipping init\n");
+		goto skip_init;
+	}
+
+	/* no infoRom script from vbios? */
+
+	/* TBD: sysmon subtask */
+
+	INIT_WORK(&pmu->isr_workq, gk20a_pmu_process_message);
+
+	ret = nvkm_gpuobj_new(nv_object(ppmu), NULL, GK20A_PMU_TRACE_BUFSIZE,
+					    0, 0, &pmu->trace_buf.obj);
+	if (ret)
+		goto err;
+	ret = nvkm_gpuobj_map_vm(nv_gpuobj(pmu->trace_buf.obj),
+					ppmuvm->vm,
+					NV_MEM_ACCESS_RW,
+					&pmu->trace_buf.vma);
+	if (ret)
+		goto map_err;
+
+	pmu->sw_ready = true;
+
+skip_init:
+	return 0;
+map_err:
+	nvkm_gpuobj_destroy(pmu->trace_buf.obj);
+err:
+	return err;
+}
+
+static int
+pmu_bootstrap(struct gk20a_pmu_priv *pmu)
+{
+	struct nvkm_pmu *ppmu = &pmu->base;
+	struct pmu_ucode_desc *desc = pmu->desc;
+	u32 addr_code, addr_data, addr_load;
+	u32 i, blocks, addr_args;
+	struct pmu_cmdline_args_gk20a cmdline_args;
+	struct nvkm_pmu_priv_vm *ppmuvm = &pmu->pmuvm;
+	nv_mask(ppmu, 0x0010a048, 0x01, 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 =
+			    lower_32_bits(pmu->trace_buf.vma.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 = lower_32_bits((pmu->ucode.vma.offset +
+			desc->app_start_offset +
+			desc->app_resident_code_offset) >> 8);
+
+	addr_data = lower_32_bits((pmu->ucode.vma.offset +
+			desc->app_start_offset +
+			desc->app_resident_data_offset) >> 8);
+
+	addr_load = lower_32_bits((pmu->ucode.vma.offset +
+			desc->bootloader_start_offset) >> 8);
+
+	nv_wr32(ppmu, 0x0010a1c4, GK20A_PMU_DMAIDX_UCODE);
+	nv_debug(ppmu, "0x%08x\n", GK20A_PMU_DMAIDX_UCODE);
+	nv_wr32(ppmu, 0x0010a1c4, (addr_code));
+	nv_debug(ppmu, "0x%08x\n", (addr_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,  (addr_data));
+	nv_debug(ppmu, "0x%08x\n", (addr_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, (addr_code));
+	nv_debug(ppmu, "0x%08x\n", (addr_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,
+		(addr_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;
+}
+
+static int
+gk20a_init_pmu_setup_hw1(struct nvkm_pmu *ppmu, struct nvkm_mc *pmc)
+{
+	struct gk20a_pmu_priv *pmu = to_gk20a_priv(ppmu);
+	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 void
+gk20a_pmu_intr(struct nvkm_subdev *subdev)
+{
+	struct nvkm_pmu *ppmu = nvkm_pmu(subdev);
+	struct gk20a_pmu_priv *pmu = to_gk20a_priv(ppmu);
+	struct nvkm_mc *pmc = nvkm_mc(ppmu);
+	u32 intr, mask;
+	if (!pmu->isr_enabled)
+		return;
+
+	mask = nv_rd32(ppmu, 0x0010a018) & nv_rd32(ppmu, 0x0010a01c);
+
+	intr = nv_rd32(ppmu, 0x0010a008) & mask;
+
+	nv_error(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");
+
+	if (intr & 0x20) {
+		nv_error(ppmu,
+			"pmu exterr intr not implemented. Clearing interrupt.");
+		nv_mask(ppmu, 0x0010a16c, (0x1 << 31), 0x00000000);
+		goto out;
+	}
+	if (intr & 0x40) {
+		nv_debug(ppmu, "scheduling work\n");
+		schedule_work(&pmu->isr_workq);
+		goto out;
+	}
+
+out:
+	pmu_enable_irq(ppmu, pmc, true);
+	nv_wr32(ppmu, 0x0010a004, intr);
+	nv_debug(ppmu, "irq handled\n");
+}
+
+static void
+gk20a_pmu_pgob(struct nvkm_pmu *ppmu, bool enable)
+{
+}
+
+static 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 gk20a_pmu_priv *pmu = to_gk20a_priv(ppmu);
+	const struct firmware *pmufw = NULL;
 	int ret;
 
-	ret = nvkm_subdev_init(&pmu->base);
+	nv_subdev(ppmu)->intr = gk20a_pmu_intr;
+
+	mutex_init(&pmu->isr_mutex);
+	mutex_init(&pmu->pmu_copy_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;
+		}
+		ret = gk20a_pmu_init_vm(ppmu, pmufw);
+		if (ret < 0) {
+			nv_error(ppmu, "failed to map pmu fw to va space\n");
+			goto 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 err;
+	}
+
+	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;
 
-	pmu->pgob = nvkm_pmu_pgob;
+	ret = nvkm_subdev_init(&ppmu->base);
+	if (ret)
+		goto err;
 
-	/* 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);
+	ppmu->pgob = nvkm_pmu_pgob;
 
-	nvkm_timer_alarm(pmu, 2000000000, &priv->alarm);
+	pmu->initialized = true;
+	/* 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(ppmu, 2000000000, &pmu->alarm);
+err:
+	gk20a_pmu_release_firmware(ppmu, pmufw);
 	return ret;
 }
 
@@ -218,13 +1023,33 @@  gk20a_pmu_ctor(struct nvkm_object *parent, struct nvkm_object *engine,
 	return 0;
 }
 
+static void
+gk20a_pmu_dtor(struct nvkm_object *object)
+{
+	struct nvkm_pmu *ppmu = (void *)object;
+	struct nvkm_mc *pmc = nvkm_mc(object);
+	struct gk20a_pmu_priv *pmu = to_gk20a_priv(ppmu);
+
+	/* 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;
+}
+
 struct nvkm_oclass *
 gk20a_pmu_oclass = &(struct nvkm_pmu_impl) {
 	.base.handle = NV_SUBDEV(PMU, 0xea),
 	.base.ofuncs = &(struct nvkm_ofuncs) {
 		.ctor = gk20a_pmu_ctor,
-		.dtor = _nvkm_pmu_dtor,
+		.dtor = gk20a_pmu_dtor,
 		.init = gk20a_pmu_init,
 		.fini = gk20a_pmu_fini,
 	},
+	.pgob = gk20a_pmu_pgob,
 }.base;