blob: bdcdf2991b5aabc7425e53431da71576e5c0fa54 [file] [log] [blame]
/* Copyright (c) 2014-2017, 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.
*/
#define pr_fmt(fmt) "AXI: NOC: %s(): " fmt, __func__
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/msm-bus-board.h>
#include <linux/msm-bus.h>
#include <linux/spinlock.h>
#include "msm_bus_core.h"
#include "msm_bus_noc.h"
#include "msm_bus_rpmh.h"
static DEFINE_SPINLOCK(noc_lock);
/* NOC_QOS generic */
#define __CLZ(x) ((8 * sizeof(uint32_t)) - 1 - __fls(x))
#define SAT_SCALE 16 /* 16 bytes minimum for saturation */
#define BW_SCALE 256 /* 1/256 byte per cycle unit */
#define QOS_DEFAULT_BASEOFFSET 0x00003000
#define QOS_DEFAULT_DELTA 0x80
#define MAX_BW_FIELD (NOC_QOS_BWn_BW_BMSK >> NOC_QOS_BWn_BW_SHFT)
#define MAX_SAT_FIELD (NOC_QOS_SATn_SAT_BMSK >> NOC_QOS_SATn_SAT_SHFT)
#define MIN_SAT_FIELD 1
#define MIN_BW_FIELD 1
#define MSM_BUS_FAB_MEM_NOC 6152
#define NOC_QOS_REG_BASE(b, o) ((b) + (o))
#define NOC_QOS_MAINCTL_LOWn_ADDR(b, o, n, d) \
(NOC_QOS_REG_BASE(b, o) + 0x8 + (d) * (n))
enum noc_qos_id_mainctl_lown {
NOC_QOS_MCTL_DFLT_PRIOn_BMSK = 0x00000070,
NOC_QOS_MCTL_DFLT_PRIOn_SHFT = 0x4,
NOC_QOS_MCTL_URGFWD_ENn_BMSK = 0x00000008,
NOC_QOS_MCTL_URGFWD_ENn_SHFT = 0x3,
NOC_QOS_MCTL_LIMIT_ENn_BMSK = 0x00000001,
NOC_QOS_MCTL_LIMIT_ENn_SHFT = 0x0,
};
#define NOC_QOS_LIMITBWn_ADDR(b, o, n, d) \
(NOC_QOS_REG_BASE(b, o) + 0x18 + (d) * (n))
enum noc_qos_id_limitbwn {
NOC_QOS_LIMITBW_BWn_BMSK = 0x000007FF,
NOC_QOS_LIMITBW_BWn_SHFT = 0x0,
NOC_QOS_LIMITBW_SATn_BMSK = 0x03FF0000,
NOC_QOS_LIMITBW_SATn_SHFT = 0x11,
};
#define NOC_QOS_REGUL0CTLn_ADDR(b, o, n, d) \
(NOC_QOS_REG_BASE(b, o) + 0x40 + (d) * (n))
enum noc_qos_id_regul0ctln {
NOC_QOS_REGUL0CTL_HI_PRIOn_BMSK = 0x00007000,
NOC_QOS_REGUL0CTL_HI_PRIOn_SHFT = 0x8,
NOC_QOS_REGUL0CTL_LW_PRIOn_BMSK = 0x00000700,
NOC_QOS_REGUL0CTL_LW_PRIOn_SHFT = 0xC,
NOC_QOS_REGUL0CTL_WRENn_BMSK = 0x00000002,
NOC_QOS_REGUL0CTL_WRENn_SHFT = 0x1,
NOC_QOS_REGUL0CTL_RDENn_BMSK = 0x00000001,
NOC_QOS_REGUL0CTL_RDENn_SHFT = 0x0,
};
#define NOC_QOS_REGUL0BWn_ADDR(b, o, n, d) \
(NOC_QOS_REG_BASE(b, o) + 0x48 + (d) * (n))
enum noc_qos_id_regul0bwbwn {
NOC_QOS_REGUL0BW_BWn_BMSK = 0x000007FF,
NOC_QOS_REGUL0BW_BWn_SHFT = 0x0,
NOC_QOS_REGUL0BW_SATn_BMSK = 0x03FF0000,
NOC_QOS_REGUL0BW_SATn_SHFT = 0x11,
};
#define NOC_QOS_MODEn_ADDR(b, o, n, d) \
(NOC_QOS_REG_BASE(b, o) + 0xC + (d) * (n))
enum noc_qos_id_moden_rmsk {
NOC_QOS_MODEn_RMSK = 0x00000003,
NOC_QOS_MODEn_MAXn = 32,
NOC_QOS_MODEn_MODE_BMSK = 0x3,
NOC_QOS_MODEn_MODE_SHFT = 0x0,
};
#define NOC_QOS_BWn_ADDR(b, o, n, d) \
(NOC_QOS_REG_BASE(b, o) + 0x10 + (d) * (n))
enum noc_qos_id_bwn {
NOC_QOS_BWn_RMSK = 0x0000ffff,
NOC_QOS_BWn_MAXn = 32,
NOC_QOS_BWn_BW_BMSK = 0xffff,
NOC_QOS_BWn_BW_SHFT = 0x0,
};
/* QOS Saturation registers */
#define NOC_QOS_SATn_ADDR(b, o, n, d) \
(NOC_QOS_REG_BASE(b, o) + 0x14 + (d) * (n))
enum noc_qos_id_saturationn {
NOC_QOS_SATn_RMSK = 0x000003ff,
NOC_QOS_SATn_MAXn = 32,
NOC_QOS_SATn_SAT_BMSK = 0x3ff,
NOC_QOS_SATn_SAT_SHFT = 0x0,
};
static void __iomem *memnoc_qos_base;
static int noc_div(uint64_t *a, uint32_t b)
{
if ((*a > 0) && (*a < b)) {
*a = 0;
return 1;
} else {
return do_div(*a, b);
}
}
/**
* Calculates bw hardware is using from register values
* bw returned is in bytes/sec
*/
static uint64_t noc_bw(uint32_t bw_field, uint32_t qos_freq)
{
uint64_t res;
uint32_t rem, scale;
res = 2 * qos_freq * bw_field;
scale = BW_SCALE * 1000;
rem = noc_div(&res, scale);
MSM_BUS_DBG("NOC: Calculated bw: %llu\n", res * 1000000ULL);
return res * 1000000ULL;
}
/**
* Calculate the max BW in Bytes/s for a given time-base.
*/
#define MAX_BW(timebase) noc_bw_ceil(MAX_BW_FIELD, (timebase))
/**
* Calculates ws hardware is using from register values
* ws returned is in nanoseconds
*/
static uint32_t noc_ws(uint64_t bw, uint32_t sat, uint32_t qos_freq)
{
if (bw && qos_freq) {
uint32_t bwf = bw * qos_freq;
uint64_t scale = 1000000000000LL * BW_SCALE *
SAT_SCALE * sat;
noc_div(&scale, bwf);
MSM_BUS_DBG("NOC: Calculated ws: %llu\n", scale);
return scale;
}
return 0;
}
#define MAX_WS(bw, timebase) noc_ws((bw), MAX_SAT_FIELD, (timebase))
static void noc_set_qos_dflt_prio(void __iomem *base, uint32_t qos_off,
uint32_t mport, uint32_t qos_delta,
uint32_t prio)
{
uint32_t reg_val, val;
reg_val = readl_relaxed(NOC_QOS_MAINCTL_LOWn_ADDR(base, qos_off, mport,
qos_delta));
val = prio << NOC_QOS_MCTL_DFLT_PRIOn_SHFT;
writel_relaxed(((reg_val & (~(NOC_QOS_MCTL_DFLT_PRIOn_BMSK))) |
(val & NOC_QOS_MCTL_DFLT_PRIOn_BMSK)),
NOC_QOS_MAINCTL_LOWn_ADDR(base, qos_off, mport, qos_delta));
/* Ensure qos priority is set before exiting */
wmb();
}
static void noc_enable_qos_limiter(void __iomem *base, uint32_t qos_off,
uint32_t mport, uint32_t qos_delta, uint32_t lim_en)
{
uint32_t reg_val, val;
reg_val = readl_relaxed(NOC_QOS_MAINCTL_LOWn_ADDR(base, qos_off, mport,
qos_delta));
val = lim_en << NOC_QOS_MCTL_LIMIT_ENn_SHFT;
writel_relaxed(((reg_val & (~(NOC_QOS_MCTL_LIMIT_ENn_BMSK))) |
(val & NOC_QOS_MCTL_LIMIT_ENn_BMSK)),
NOC_QOS_MAINCTL_LOWn_ADDR(base, qos_off, mport, qos_delta));
/* Ensure we disable/enable limiter before exiting*/
wmb();
}
static void noc_set_qos_limit_bw(void __iomem *base, uint32_t qos_off,
uint32_t mport, uint32_t qos_delta, uint32_t bw)
{
uint32_t reg_val, val;
reg_val = readl_relaxed(NOC_QOS_LIMITBWn_ADDR(base, qos_off, mport,
qos_delta));
val = bw << NOC_QOS_LIMITBW_BWn_SHFT;
writel_relaxed(((reg_val & (~(NOC_QOS_LIMITBW_BWn_BMSK))) |
(val & NOC_QOS_LIMITBW_BWn_BMSK)),
NOC_QOS_LIMITBWn_ADDR(base, qos_off, mport, qos_delta));
/* Ensure we set limiter bw before exiting*/
wmb();
}
static void noc_set_qos_limit_sat(void __iomem *base, uint32_t qos_off,
uint32_t mport, uint32_t qos_delta, uint32_t sat)
{
uint32_t reg_val, val;
reg_val = readl_relaxed(NOC_QOS_LIMITBWn_ADDR(base, qos_off, mport,
qos_delta));
val = sat << NOC_QOS_LIMITBW_SATn_SHFT;
writel_relaxed(((reg_val & (~(NOC_QOS_LIMITBW_SATn_BMSK))) |
(val & NOC_QOS_LIMITBW_SATn_BMSK)),
NOC_QOS_LIMITBWn_ADDR(base, qos_off, mport, qos_delta));
/* Ensure we set limiter sat before exiting*/
wmb();
}
static void noc_set_qos_limiter(void __iomem *base, uint32_t qos_off,
uint32_t mport, uint32_t qos_delta,
struct msm_bus_noc_limiter *lim, uint32_t lim_en)
{
noc_enable_qos_limiter(base, qos_off, mport, qos_delta, 0);
noc_set_qos_limit_bw(base, qos_off, mport, qos_delta, lim->bw);
noc_set_qos_limit_sat(base, qos_off, mport, qos_delta, lim->sat);
noc_enable_qos_limiter(base, qos_off, mport, qos_delta, lim_en);
}
static void noc_set_qos_regulator(void __iomem *base, uint32_t qos_off,
uint32_t mport, uint32_t qos_delta,
struct msm_bus_noc_regulator *reg,
struct msm_bus_noc_regulator_mode *reg_mode)
{
uint32_t reg_val, val;
reg_val = readl_relaxed(NOC_QOS_REGUL0CTLn_ADDR(base, qos_off, mport,
qos_delta)) & (NOC_QOS_REGUL0CTL_WRENn_BMSK |
NOC_QOS_REGUL0CTL_RDENn_BMSK);
writel_relaxed((reg_val & (~(NOC_QOS_REGUL0CTL_WRENn_BMSK |
NOC_QOS_REGUL0CTL_RDENn_BMSK))),
NOC_QOS_REGUL0CTLn_ADDR(base, qos_off, mport, qos_delta));
/* Ensure qos regulator is disabled before configuring */
wmb();
reg_val = readl_relaxed(NOC_QOS_REGUL0CTLn_ADDR(base, qos_off, mport,
qos_delta)) & NOC_QOS_REGUL0CTL_HI_PRIOn_BMSK;
val = reg->hi_prio << NOC_QOS_REGUL0CTL_HI_PRIOn_SHFT;
writel_relaxed(((reg_val & (~(NOC_QOS_REGUL0CTL_HI_PRIOn_BMSK))) |
(val & NOC_QOS_REGUL0CTL_HI_PRIOn_BMSK)),
NOC_QOS_REGUL0CTLn_ADDR(base, qos_off, mport, qos_delta));
reg_val = readl_relaxed(NOC_QOS_REGUL0CTLn_ADDR(base, qos_off, mport,
qos_delta)) & NOC_QOS_REGUL0CTL_LW_PRIOn_BMSK;
val = reg->low_prio << NOC_QOS_REGUL0CTL_LW_PRIOn_SHFT;
writel_relaxed(((reg_val & (~(NOC_QOS_REGUL0CTL_LW_PRIOn_BMSK))) |
(val & NOC_QOS_REGUL0CTL_LW_PRIOn_BMSK)),
NOC_QOS_REGUL0CTLn_ADDR(base, qos_off, mport, qos_delta));
reg_val = readl_relaxed(NOC_QOS_REGUL0BWn_ADDR(base, qos_off, mport,
qos_delta)) & NOC_QOS_REGUL0BW_BWn_BMSK;
val = reg->bw << NOC_QOS_REGUL0BW_BWn_SHFT;
writel_relaxed(((reg_val & (~(NOC_QOS_REGUL0BW_BWn_BMSK))) |
(val & NOC_QOS_REGUL0BW_BWn_BMSK)),
NOC_QOS_REGUL0BWn_ADDR(base, qos_off, mport, qos_delta));
reg_val = readl_relaxed(NOC_QOS_REGUL0BWn_ADDR(base, qos_off, mport,
qos_delta)) & NOC_QOS_REGUL0BW_SATn_BMSK;
val = reg->sat << NOC_QOS_REGUL0BW_SATn_SHFT;
writel_relaxed(((reg_val & (~(NOC_QOS_REGUL0BW_SATn_BMSK))) |
(val & NOC_QOS_REGUL0BW_SATn_BMSK)),
NOC_QOS_REGUL0BWn_ADDR(base, qos_off, mport, qos_delta));
/* Ensure regulator is configured before possibly enabling */
wmb();
reg_val = readl_relaxed(NOC_QOS_REGUL0CTLn_ADDR(base, qos_off, mport,
qos_delta));
val = reg_mode->write << NOC_QOS_REGUL0CTL_WRENn_SHFT;
writel_relaxed(((reg_val & (~(NOC_QOS_REGUL0CTL_WRENn_BMSK))) |
(val & NOC_QOS_REGUL0CTL_WRENn_BMSK)),
NOC_QOS_REGUL0CTLn_ADDR(base, qos_off, mport, qos_delta));
reg_val = readl_relaxed(NOC_QOS_REGUL0CTLn_ADDR(base, qos_off, mport,
qos_delta));
val = reg_mode->read << NOC_QOS_REGUL0CTL_RDENn_SHFT;
writel_relaxed(((reg_val & (~(NOC_QOS_REGUL0CTL_RDENn_BMSK))) |
(val & NOC_QOS_REGUL0CTL_RDENn_BMSK)),
NOC_QOS_REGUL0CTLn_ADDR(base, qos_off, mport, qos_delta));
/* Ensure regulator is ready before exiting */
wmb();
}
static void noc_set_qos_forwarding(void __iomem *base, uint32_t qos_off,
uint32_t mport, uint32_t qos_delta,
bool urg_fwd_en)
{
uint32_t reg_val, val;
reg_val = readl_relaxed(NOC_QOS_MAINCTL_LOWn_ADDR(base, qos_off, mport,
qos_delta));
val = (urg_fwd_en ? 1:0) << NOC_QOS_MCTL_URGFWD_ENn_SHFT;
writel_relaxed(((reg_val & (~(NOC_QOS_MCTL_URGFWD_ENn_BMSK))) |
(val & NOC_QOS_MCTL_URGFWD_ENn_BMSK)),
NOC_QOS_MAINCTL_LOWn_ADDR(base, qos_off, mport, qos_delta));
/* Ensure qos priority is set before exiting */
wmb();
}
void msm_bus_noc_get_qos_bw(void __iomem *base, uint32_t qos_off,
uint32_t qos_freq,
uint32_t mport, uint32_t qos_delta, uint8_t perm_mode,
struct msm_bus_noc_qos_bw *qbw)
{
if (perm_mode & (NOC_QOS_PERM_MODE_LIMITER |
NOC_QOS_PERM_MODE_REGULATOR)) {
uint32_t bw_val = readl_relaxed(NOC_QOS_BWn_ADDR(
base, qos_off, mport, qos_delta)) & NOC_QOS_BWn_BW_BMSK;
uint32_t sat = readl_relaxed(NOC_QOS_SATn_ADDR(
base, qos_off, mport, qos_delta))
& NOC_QOS_SATn_SAT_BMSK;
qbw->bw = noc_bw(bw_val, qos_freq);
qbw->ws = noc_ws(qbw->bw, sat, qos_freq);
} else {
qbw->bw = 0;
qbw->ws = 0;
}
}
static int msm_bus_noc_qos_init(struct msm_bus_node_device_type *info,
struct msm_bus_node_device_type *fabdev,
void __iomem *qos_base,
uint32_t qos_off, uint32_t qos_delta,
uint32_t qos_freq)
{
struct msm_bus_noc_qos_params *qos_params;
int ret = 0;
int i;
unsigned long flags;
qos_params = &info->node_info->qos_params;
if (!info->node_info->qport) {
MSM_BUS_DBG("No QoS Ports to init\n");
ret = 0;
goto err_qos_init;
}
spin_lock_irqsave(&noc_lock, flags);
if (fabdev->node_info->id == MSM_BUS_FAB_MEM_NOC)
memnoc_qos_base = qos_base;
for (i = 0; i < info->node_info->num_qports; i++) {
noc_set_qos_dflt_prio(qos_base, qos_off,
info->node_info->qport[i],
qos_delta,
qos_params->prio_dflt);
noc_set_qos_limiter(qos_base, qos_off,
info->node_info->qport[i],
qos_delta,
&qos_params->limiter,
qos_params->limiter_en);
noc_set_qos_regulator(qos_base, qos_off,
info->node_info->qport[i],
qos_delta,
&qos_params->reg,
&qos_params->reg_mode);
noc_set_qos_forwarding(qos_base, qos_off,
info->node_info->qport[i],
qos_delta,
qos_params->urg_fwd_en);
}
spin_unlock_irqrestore(&noc_lock, flags);
err_qos_init:
return ret;
}
int msm_bus_noc_throttle_wa(bool enable)
{
unsigned long flags;
spin_lock_irqsave(&noc_lock, flags);
if (!memnoc_qos_base) {
MSM_BUS_ERR("Memnoc QoS Base address not found!");
goto noc_throttle_exit;
}
if (enable) {
noc_set_qos_limit_bw(memnoc_qos_base, 0x10000, 2,
0x1000, 0x1B);
noc_set_qos_limit_bw(memnoc_qos_base, 0x10000, 3,
0x1000, 0x1B);
noc_set_qos_limit_bw(memnoc_qos_base, 0x10000, 10,
0x1000, 0x30);
noc_set_qos_limit_bw(memnoc_qos_base, 0x10000, 11,
0x1000, 0x30);
noc_enable_qos_limiter(memnoc_qos_base, 0x10000, 10,
0x1000, 1);
noc_enable_qos_limiter(memnoc_qos_base, 0x10000, 11,
0x1000, 1);
noc_enable_qos_limiter(memnoc_qos_base, 0x10000, 2,
0x1000, 1);
noc_enable_qos_limiter(memnoc_qos_base, 0x10000, 3,
0x1000, 1);
} else {
noc_enable_qos_limiter(memnoc_qos_base, 0x10000, 2,
0x1000, 0);
noc_enable_qos_limiter(memnoc_qos_base, 0x10000, 3,
0x1000, 0);
noc_enable_qos_limiter(memnoc_qos_base, 0x10000, 10,
0x1000, 0);
noc_enable_qos_limiter(memnoc_qos_base, 0x10000, 11,
0x1000, 0);
noc_set_qos_limit_bw(memnoc_qos_base, 0x10000, 2,
0x1000, 0);
noc_set_qos_limit_bw(memnoc_qos_base, 0x10000, 3,
0x1000, 0);
noc_set_qos_limit_bw(memnoc_qos_base, 0x10000, 10,
0x1000, 0);
noc_set_qos_limit_bw(memnoc_qos_base, 0x10000, 11,
0x1000, 0);
}
noc_throttle_exit:
spin_unlock_irqrestore(&noc_lock, flags);
return 0;
}
EXPORT_SYMBOL(msm_bus_noc_throttle_wa);
int msm_bus_noc_priority_wa(bool enable)
{
unsigned long flags;
spin_lock_irqsave(&noc_lock, flags);
if (!memnoc_qos_base) {
MSM_BUS_ERR("Memnoc QoS Base address not found!");
goto noc_priority_exit;
}
if (enable)
noc_set_qos_dflt_prio(memnoc_qos_base, 0x10000, 0,
0x1000, 7);
else
noc_set_qos_dflt_prio(memnoc_qos_base, 0x10000, 0,
0x1000, 6);
noc_priority_exit:
spin_unlock_irqrestore(&noc_lock, flags);
return 0;
}
EXPORT_SYMBOL(msm_bus_noc_priority_wa);
int msm_bus_noc_set_ops(struct msm_bus_node_device_type *bus_dev)
{
if (!bus_dev)
return -ENODEV;
bus_dev->fabdev->noc_ops.qos_init = msm_bus_noc_qos_init;
return 0;
}
EXPORT_SYMBOL(msm_bus_noc_set_ops);