271 lines
7.7 KiB
C
271 lines
7.7 KiB
C
/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
/**
|
|
* This file contains the part of the IOMMUv1 PMU driver that actually touches
|
|
* IOMMU PMU registers.
|
|
*/
|
|
|
|
#include <linux/io.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
#include <mach/iommu_hw-v1.h>
|
|
#include <mach/iommu_perfmon.h>
|
|
#include <mach/iommu.h>
|
|
|
|
#define PMCR_P_MASK (0x1)
|
|
#define PMCR_P_SHIFT (1)
|
|
#define PMCR_P (PMCR_P_MASK << PMCR_P_SHIFT)
|
|
#define PMCFGR_NCG_MASK (0xFF)
|
|
#define PMCFGR_NCG_SHIFT (24)
|
|
#define PMCFGR_NCG (PMCFGR_NCG_MASK << PMCFGR_NCG_SHIFT)
|
|
#define PMCFGR_N_MASK (0xFF)
|
|
#define PMCFGR_N_SHIFT (0)
|
|
#define PMCFGR_N (PMCFGR_N_MASK << PMCFGR_N_SHIFT)
|
|
#define CR_E 0x1
|
|
#define CGCR_CEN 0x800
|
|
#define CGCR_CEN_SHFT (1 << 11)
|
|
#define PMCGCR_CGNC_MASK (0x0F)
|
|
#define PMCGCR_CGNC_SHIFT (24)
|
|
#define PMCGCR_CGNC (PMCGCR_CGNC_MASK << PMCGCR_CGNC_SHIFT)
|
|
#define PMCGCR_(group) (PMCGCR_N + group*4)
|
|
|
|
#define PMOVSCLR_(n) (PMOVSCLR_N + n*4)
|
|
#define PMCNTENSET_(n) (PMCNTENSET_N + n*4)
|
|
#define PMCNTENCLR_(n) (PMCNTENCLR_N + n*4)
|
|
#define PMINTENSET_(n) (PMINTENSET_N + n*4)
|
|
#define PMINTENCLR_(n) (PMINTENCLR_N + n*4)
|
|
|
|
#define PMEVCNTR_(n) (PMEVCNTR_N + n*4)
|
|
#define PMEVTYPER_(n) (PMEVTYPER_N + n*4)
|
|
|
|
|
|
static unsigned int iommu_pm_is_hw_access_OK(const struct iommu_pmon *pmon)
|
|
{
|
|
/*
|
|
* IOMMUv1 is not in the always on domain so we need to make sure
|
|
* the regulators are turned on in addition to clocks before we allow
|
|
* access to the hardware thus we check if we have attached to the
|
|
* IOMMU in addition to checking if we have enabled PMU.
|
|
*/
|
|
return pmon->enabled && (pmon->iommu_attach_count > 0);
|
|
}
|
|
|
|
static void iommu_pm_grp_enable(struct iommu_info *iommu, unsigned int grp_no)
|
|
{
|
|
unsigned int pmcgcr;
|
|
pmcgcr = readl_relaxed(iommu->base + PMCGCR_(grp_no));
|
|
pmcgcr |= CGCR_CEN;
|
|
writel_relaxed(pmcgcr, iommu->base + PMCGCR_(grp_no));
|
|
}
|
|
|
|
static void iommu_pm_grp_disable(struct iommu_info *iommu, unsigned int grp_no)
|
|
{
|
|
unsigned int pmcgcr;
|
|
pmcgcr = readl_relaxed(iommu->base + PMCGCR_(grp_no));
|
|
pmcgcr &= ~CGCR_CEN;
|
|
writel_relaxed(pmcgcr, iommu->base + PMCGCR_(grp_no));
|
|
}
|
|
|
|
static void iommu_pm_enable(struct iommu_info *iommu)
|
|
{
|
|
unsigned int pmcr;
|
|
pmcr = readl_relaxed(iommu->base + PMCR);
|
|
pmcr |= CR_E;
|
|
writel_relaxed(pmcr, iommu->base + PMCR);
|
|
}
|
|
|
|
static void iommu_pm_disable(struct iommu_info *iommu)
|
|
{
|
|
unsigned int pmcr;
|
|
pmcr = readl_relaxed(iommu->base + PMCR);
|
|
pmcr &= ~CR_E;
|
|
writel_relaxed(pmcr, iommu->base + PMCR);
|
|
}
|
|
|
|
static void iommu_pm_reset_counters(const struct iommu_info *iommu)
|
|
{
|
|
unsigned int pmcr;
|
|
pmcr = readl_relaxed(iommu->base + PMCR);
|
|
pmcr |= PMCR_P;
|
|
writel_relaxed(pmcr, iommu->base + PMCR);
|
|
}
|
|
|
|
static void iommu_pm_check_for_overflow(struct iommu_pmon *pmon)
|
|
{
|
|
struct iommu_pmon_counter *counter;
|
|
struct iommu_info *iommu = &pmon->iommu;
|
|
unsigned int reg_no = 0;
|
|
unsigned int bit_no;
|
|
unsigned int reg_value;
|
|
unsigned int i;
|
|
unsigned int j;
|
|
unsigned int curr_reg = 0;
|
|
|
|
reg_value = readl_relaxed(iommu->base + PMOVSCLR_(curr_reg));
|
|
|
|
for (i = 0; i < pmon->num_groups; ++i) {
|
|
struct iommu_pmon_cnt_group *cnt_grp = &pmon->cnt_grp[i];
|
|
for (j = 0; j < cnt_grp->num_counters; ++j) {
|
|
counter = &cnt_grp->counters[j];
|
|
reg_no = counter->absolute_counter_no / 32;
|
|
bit_no = counter->absolute_counter_no % 32;
|
|
if (reg_no != curr_reg) {
|
|
/* Clear overflow bits */
|
|
writel_relaxed(reg_value, iommu->base +
|
|
PMOVSCLR_(reg_no));
|
|
curr_reg = reg_no;
|
|
reg_value = readl_relaxed(iommu->base +
|
|
PMOVSCLR_(curr_reg));
|
|
}
|
|
|
|
if (counter->enabled) {
|
|
if (reg_value & (1 << bit_no))
|
|
counter->overflow_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Clear overflow */
|
|
writel_relaxed(reg_value, iommu->base + PMOVSCLR_(reg_no));
|
|
}
|
|
|
|
static irqreturn_t iommu_pm_evt_ovfl_int_handler(int irq, void *dev_id)
|
|
{
|
|
struct iommu_pmon *pmon = dev_id;
|
|
struct iommu_info *iommu = &pmon->iommu;
|
|
|
|
mutex_lock(&pmon->lock);
|
|
|
|
if (!iommu_pm_is_hw_access_OK(pmon)) {
|
|
mutex_unlock(&pmon->lock);
|
|
goto out;
|
|
}
|
|
|
|
iommu->ops->iommu_lock_acquire();
|
|
iommu_pm_check_for_overflow(pmon);
|
|
iommu->ops->iommu_lock_release();
|
|
|
|
mutex_unlock(&pmon->lock);
|
|
|
|
out:
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void iommu_pm_counter_enable(struct iommu_info *iommu,
|
|
struct iommu_pmon_counter *counter)
|
|
{
|
|
unsigned int reg_no = counter->absolute_counter_no / 32;
|
|
unsigned int bit_no = counter->absolute_counter_no % 32;
|
|
unsigned int reg_value;
|
|
|
|
/* Clear overflow of counter */
|
|
reg_value = 1 << bit_no;
|
|
writel_relaxed(reg_value, iommu->base + PMOVSCLR_(reg_no));
|
|
|
|
/* Enable counter */
|
|
writel_relaxed(reg_value, iommu->base + PMCNTENSET_(reg_no));
|
|
counter->enabled = 1;
|
|
}
|
|
|
|
static void iommu_pm_counter_disable(struct iommu_info *iommu,
|
|
struct iommu_pmon_counter *counter)
|
|
{
|
|
unsigned int reg_no = counter->absolute_counter_no / 32;
|
|
unsigned int bit_no = counter->absolute_counter_no % 32;
|
|
unsigned int reg_value;
|
|
|
|
counter->enabled = 0;
|
|
|
|
/* Disable counter */
|
|
reg_value = 1 << bit_no;
|
|
writel_relaxed(reg_value, iommu->base + PMCNTENCLR_(reg_no));
|
|
|
|
/* Clear overflow of counter */
|
|
writel_relaxed(reg_value, iommu->base + PMOVSCLR_(reg_no));
|
|
}
|
|
|
|
/*
|
|
* Must be called after iommu_start_access() is called
|
|
*/
|
|
static void iommu_pm_ovfl_int_enable(struct iommu_info *iommu,
|
|
const struct iommu_pmon_counter *counter)
|
|
{
|
|
unsigned int reg_no = counter->absolute_counter_no / 32;
|
|
unsigned int bit_no = counter->absolute_counter_no % 32;
|
|
unsigned int reg_value;
|
|
|
|
/* Enable overflow interrupt for counter */
|
|
reg_value = (1 << bit_no);
|
|
writel_relaxed(reg_value, iommu->base + PMINTENSET_(reg_no));
|
|
}
|
|
|
|
/*
|
|
* Must be called after iommu_start_access() is called
|
|
*/
|
|
static void iommu_pm_ovfl_int_disable(struct iommu_info *iommu,
|
|
const struct iommu_pmon_counter *counter)
|
|
{
|
|
unsigned int reg_no = counter->absolute_counter_no / 32;
|
|
unsigned int bit_no = counter->absolute_counter_no % 32;
|
|
unsigned int reg_value;
|
|
|
|
/* Disable overflow interrupt for counter */
|
|
reg_value = 1 << bit_no;
|
|
writel_relaxed(reg_value, iommu->base + PMINTENCLR_(reg_no));
|
|
}
|
|
|
|
static void iommu_pm_set_event_class(struct iommu_pmon *pmon,
|
|
unsigned int count_no,
|
|
unsigned int event_class)
|
|
{
|
|
writel_relaxed(event_class, pmon->iommu.base + PMEVTYPER_(count_no));
|
|
}
|
|
|
|
static unsigned int iommu_pm_read_counter(struct iommu_pmon_counter *counter)
|
|
{
|
|
struct iommu_pmon *pmon = counter->cnt_group->pmon;
|
|
struct iommu_info *info = &pmon->iommu;
|
|
unsigned int cnt_no = counter->absolute_counter_no;
|
|
return readl_relaxed(info->base + PMEVCNTR_(cnt_no));
|
|
}
|
|
|
|
static void iommu_pm_initialize_hw(const struct iommu_pmon *pmon)
|
|
{
|
|
/* No initialization needed */
|
|
}
|
|
|
|
static struct iommu_pm_hw_ops iommu_pm_hw_ops = {
|
|
.initialize_hw = iommu_pm_initialize_hw,
|
|
.is_hw_access_OK = iommu_pm_is_hw_access_OK,
|
|
.grp_enable = iommu_pm_grp_enable,
|
|
.grp_disable = iommu_pm_grp_disable,
|
|
.enable_pm = iommu_pm_enable,
|
|
.disable_pm = iommu_pm_disable,
|
|
.reset_counters = iommu_pm_reset_counters,
|
|
.check_for_overflow = iommu_pm_check_for_overflow,
|
|
.evt_ovfl_int_handler = iommu_pm_evt_ovfl_int_handler,
|
|
.counter_enable = iommu_pm_counter_enable,
|
|
.counter_disable = iommu_pm_counter_disable,
|
|
.ovfl_int_enable = iommu_pm_ovfl_int_enable,
|
|
.ovfl_int_disable = iommu_pm_ovfl_int_disable,
|
|
.set_event_class = iommu_pm_set_event_class,
|
|
.read_counter = iommu_pm_read_counter,
|
|
};
|
|
|
|
struct iommu_pm_hw_ops *iommu_pm_get_hw_ops_v1(void)
|
|
{
|
|
return &iommu_pm_hw_ops;
|
|
}
|
|
EXPORT_SYMBOL(iommu_pm_get_hw_ops_v1);
|
|
|