blob: 8fa6dae74ecb7d8ec463c9f2dda7f24b0889e80e [file] [log] [blame]
/*
* Copyright (C) 2019 Samsung Electronics Co., Ltd.
*
* Authors: Shaik Ameer Basha(shaik.ameer@samsung.com)
*
* Airbrush DDR Performance profiling driver.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*/
#include <linux/airbrush-sm-ctrl.h>
#include <linux/delay.h>
#include <linux/pci.h>
#include "airbrush-ddr.h"
#include "airbrush-ddr-internal.h"
#include "airbrush-pmic-ctrl.h"
#include "airbrush-regs.h"
#define ABC_MSI_6_PPC_MIF (6)
static bool ddr_ppc_is_event_valid(uint32_t event)
{
switch (event) {
case PPC_EVENT_WR_PORT0:
case PPC_EVENT_RD_PORT0:
case PPC_EVENT_WR_PORT1:
case PPC_EVENT_RD_PORT1:
case PPC_EVENT_WR_PORT2:
case PPC_EVENT_RD_PORT2:
case PPC_EVENT_WR_PORT3:
case PPC_EVENT_RD_PORT3:
case PPC_EVENT_WR2BANK0_PORT2:
case PPC_EVENT_WR2BANK1_PORT2:
case PPC_EVENT_WR2BANK2_PORT2:
case PPC_EVENT_WR2BANK3_PORT2:
case PPC_EVENT_WR_CAS:
case PPC_EVENT_RD_CAS:
case PPC_EVENT_RW_CAS:
case PPC_EVENT_WR_SHED:
case PPC_EVENT_RD_SHED:
case PPC_EVENT_RW_SHED:
case PPC_EVENT_WR_XFER:
case PPC_EVENT_RD_XFER:
case PPC_EVENT_RW_XFER:
case PPC_EVENT_PD_CHIP0:
case PPC_EVENT_PD_CHIP1:
case PPC_EVENT_SREF_CHIP0:
case PPC_EVENT_SREF_CHIP1:
case PPC_EVENT_AXI_RD_REQ_PORT0:
case PPC_EVENT_AXI_RD_REQ_PORT1:
case PPC_EVENT_AXI_RD_REQ_PORT2:
case PPC_EVENT_AXI_RD_REQ_PORT3:
case PPC_EVENT_AXI_WR_REQ_PORT0:
case PPC_EVENT_AXI_WR_REQ_PORT1:
case PPC_EVENT_AXI_WR_REQ_PORT2:
case PPC_EVENT_AXI_WR_REQ_PORT3:
return true;
default:
return false;
}
}
static int ddr_ppc_irq_callback(uint32_t irq, void *data)
{
int i;
uint32_t ppc_overflow_data;
struct ddr_ppc_overflow_info *ppcinfo;
struct ab_ddr_context *ddr_ctx = (struct ab_ddr_context *)data;
pr_info("ddr ppc callback is called for irq %d\n", irq);
if (!ddr_ctx) {
pr_err("ddr ppc callback: invalid data received to callback");
return -EINVAL;
}
ppcinfo = &ddr_ctx->ddr_ppc_info;
ppc_overflow_data = ddr_reg_rd(ddr_ctx, DREX_FLAG_PCC);
while (ppc_overflow_data) {
if (ppc_overflow_data & PPC_CCNT_FLAG) {
ddr_reg_set(ddr_ctx, DREX_FLAG_PCC, PPC_CCNT_FLAG);
ppcinfo->overflow_count_cycle_cnt++;
}
for (i = 0; i < PPC_COUNTER_MAX; i++) {
if (ppc_overflow_data & PPC_PMCNT_FLAG(i)) {
ddr_reg_set(ddr_ctx,
DREX_FLAG_PCC, PPC_PMCNT_FLAG(i));
ppcinfo->overflow_count_cnt[i]++;
}
}
ppc_overflow_data = ddr_reg_rd(ddr_ctx, DREX_FLAG_PCC);
}
return 0;
}
static void ddr_ppc_irq_register(struct ab_ddr_context *ddr_ctx)
{
/* Register callback with callback function & MSI_IRQ Number */
abc_reg_irq_callback(&ddr_ppc_irq_callback,
ABC_MSI_6_PPC_MIF, ddr_ctx);
/* Enable the overflow interrupts */
ddr_reg_wr(ddr_ctx, DREX_INTENS_PCC,
PPC_PMCNT_INTSET(0) | PPC_PMCNT_INTSET(1) |
PPC_PMCNT_INTSET(2) | PPC_PMCNT_INTSET(3) |
PPC_CCNT_INTSET);
}
static void ddr_ppc_irq_deregister(struct ab_ddr_context *ddr_ctx)
{
/* Disable the overflow interrupts */
ddr_reg_wr(ddr_ctx, DREX_INTENC_PCC,
PPC_PMCNT_INTCLR(0) | PPC_PMCNT_INTCLR(1) |
PPC_PMCNT_INTCLR(2) | PPC_PMCNT_INTCLR(3) |
PPC_CCNT_INTCLR);
/* De-Register callback for ABC_MSI_6_PPC_MIF MSI interrupt */
abc_reg_irq_callback(NULL, ABC_MSI_6_PPC_MIF, NULL);
}
/* Caller must hold ddr_ctx->ddr_lock */
static void ab_ddr_ppc_start(struct ab_ddr_context *ddr_ctx)
{
int i;
struct ddr_ppc_overflow_info *ppcinfo = &ddr_ctx->ddr_ppc_info;
memset(ppcinfo, 0, sizeof(*ppcinfo));
/* Performance Event Clock Enable */
ddr_reg_set(ddr_ctx, DREX_PPCCLKCON, PEREV_CLK_EN);
ddr_reg_wr(ddr_ctx, DREX_CNTENS_PCC, 0);
ddr_reg_set(ddr_ctx, DREX_CNTENS_PCC, PPC_CCNT_ENABLE);
for (i = 0; i < PPC_COUNTER_MAX; i++) {
if (ddr_ppc_is_event_valid(ddr_ctx->ddr_ppc_events[i])) {
ddr_reg_wr(ddr_ctx, DREX_PEREVCONFIG(i),
PEREVx_SEL(ddr_ctx->ddr_ppc_events[i]));
ddr_reg_set(ddr_ctx, DREX_CNTENS_PCC,
PPC_PMCNT_ENABLE(i));
}
}
ddr_ppc_irq_register(ddr_ctx);
ddr_reg_wr(ddr_ctx, DREX_PMNC_PCC, CYCLECNT_RESET | PPC_COUNTER_RESET);
ddr_reg_set(ddr_ctx, DREX_PMNC_PCC, PPC_ENABLE);
}
/* Caller must hold ddr_ctx->ddr_lock */
static void ab_ddr_ppc_stop(struct ab_ddr_context *ddr_ctx)
{
int i;
struct ddr_ppc_overflow_info *ppcinfo = &ddr_ctx->ddr_ppc_info;
ddr_reg_clr(ddr_ctx, DREX_PMNC_PCC, PPC_ENABLE);
/* Print the overflow and event count information */
pr_info("CCNT: overflow_cnt: %d, event_cnt: %u\n",
ppcinfo->overflow_count_cycle_cnt,
ddr_reg_rd(ddr_ctx, DREX_CCCNT_PPC));
for (i = 0; i < PPC_COUNTER_MAX; i++) {
pr_info("PMCNT[%d]: overflow_cnt: %d, event_cnt: %u\n",
i, ppcinfo->overflow_count_cnt[i],
ddr_reg_rd(ddr_ctx, DREX_PMCNT_PPC(i)));
}
/* Disable the count registers */
ddr_reg_wr(ddr_ctx, DREX_CNTENC_PCC, PPC_PMCNT_DISABLE(0) |
PPC_PMCNT_DISABLE(1) |
PPC_PMCNT_DISABLE(2) |
PPC_PMCNT_DISABLE(3) |
PPC_CCNT_DISABLE);
ddr_ppc_irq_deregister(ddr_ctx);
ddr_reg_wr(ddr_ctx, DREX_PMNC_PCC, CYCLECNT_RESET | PPC_COUNTER_RESET);
/* Performance Event Clock Disable */
ddr_reg_clr(ddr_ctx, DREX_PPCCLKCON, PEREV_CLK_EN);
}
int ab_ddr_ppc_set_event(void *ctx, unsigned int counter_idx,
unsigned int event)
{
struct ab_ddr_context *ddr_ctx = (struct ab_ddr_context *)ctx;
if (!ddr_ctx->is_setup_done) {
pr_err("ppc set event: error!! ddr setup is not called\n");
return -EAGAIN;
}
if (counter_idx >= PPC_COUNTER_MAX) {
pr_err("ppc set event: Invalid DDR PPC counter\n");
return -EINVAL;
}
mutex_lock(&ddr_ctx->ddr_lock);
/* set event counters */
if (!ddr_ppc_is_event_valid(event))
ddr_ctx->ddr_ppc_events[counter_idx] = -1;
else
ddr_ctx->ddr_ppc_events[counter_idx] = event;
mutex_unlock(&ddr_ctx->ddr_lock);
pr_info("[ddr ppc]: counter: %d, event: 0x%x\n", counter_idx, event);
return 0;
}
void ab_ddr_ppc_ctrl(void *ctx, int ppc_start)
{
struct ab_ddr_context *ddr_ctx = (struct ab_ddr_context *)ctx;
static int prev_ppc_start;
if (!ddr_ctx->is_setup_done) {
pr_err("ddr ppc ctrl: error!! ddr setup is not called\n");
return;
}
/* don't allow ppc_stop without a previous ppc_start request */
if (!ppc_start && !prev_ppc_start) {
pr_err("ddr ppc ctrl: error!! ppc_start is not called\n");
return;
}
mutex_lock(&ddr_ctx->ddr_lock);
if (ppc_start)
ab_ddr_ppc_start(ddr_ctx);
else
ab_ddr_ppc_stop(ddr_ctx);
mutex_unlock(&ddr_ctx->ddr_lock);
prev_ppc_start = ppc_start;
}