blob: fa834a0bedade02bae5bbe6ada1908c9d4ebbcc9 [file] [log] [blame]
/*
* Copyright (c) 2015 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/pm_qos.h>
#include <linux/suspend.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <soc/samsung/bts.h>
#include "cal_bts8895.h"
#define BTS_DBG(x...) if (exynos_bts_log) pr_info(x)
#define MIF_UTIL 65
#define INT_UTIL 70
#define INITIAL_MIF_FREQ 2093000
#define QBUSY_DEFAULT 0
#define QFULL_LOW_DEFAULT 0x0
#define QFULL_HIGH_DEFAULT 0x6
static unsigned int exynos_qbusy = QBUSY_DEFAULT;
static unsigned int exynos_qfull_low = QFULL_LOW_DEFAULT;
static unsigned int exynos_qfull_high = QFULL_HIGH_DEFAULT;
static int exynos_bts_log;
static unsigned int exynos_mif_util = MIF_UTIL;
static unsigned int exynos_int_util = INT_UTIL;
static unsigned int default_exynos_qmax_r[] = {0xffff};
static unsigned int default_exynos_qmax_w[] = {0xffff};
static unsigned int default_exynos_tq[] = {0};
static unsigned int *exynos_qmax_r;
static unsigned int exynos_nqmax_r;
static unsigned int *exynos_qmax_w;
static unsigned int exynos_nqmax_w;
static unsigned int *exynos_tq;
static unsigned int exynos_ntq;
static unsigned int exynos_mif_freq = INITIAL_MIF_FREQ;
enum bts_index {
BTS_IDX_CP,
BTS_IDX_DPU0,
BTS_IDX_DPU1,
BTS_IDX_DPU2,
BTS_IDX_CAM0,
BTS_IDX_CAM1,
BTS_IDX_ISPLP,
BTS_IDX_SRDZ,
#ifndef CONFIG_SOC_EMULATOR8895
BTS_IDX_IVA,
BTS_IDX_DSP,
#endif
BTS_IDX_VPU,
BTS_IDX_MFC0,
BTS_IDX_MFC1,
BTS_IDX_G2D0,
BTS_IDX_G2D1,
BTS_IDX_G2D2,
BTS_IDX_ALIVE,
BTS_IDX_FSYS0,
BTS_IDX_PDMA,
BTS_IDX_SPDMA,
BTS_IDX_ABOX,
BTS_IDX_VTS,
BTS_IDX_FSYS1,
BTS_IDX_GNSS,
BTS_IDX_G3D0,
BTS_IDX_G3D1,
BTS_IDX_G3D2,
BTS_IDX_G3D3,
};
enum exynos_bts_type {
BT_TREX,
};
struct bts_table {
struct bts_status stat;
struct bts_info *next_bts;
int prev_scen;
int next_scen;
};
struct bts_info {
const char *name;
unsigned int pa_base;
void __iomem *va_base;
bool enable;
enum exynos_bts_type type;
struct bts_table table[BS_MAX];
enum bts_scen_type top_scen;
};
struct bts_scenario {
const char *name;
struct bts_info *head;
};
static struct pm_qos_request exynos_mif_bts_qos;
static struct pm_qos_request exynos_int_bts_qos;
static DEFINE_MUTEX(media_mutex);
struct trex_info {
unsigned int pa_base;
void __iomem *va_base;
unsigned int value;
unsigned int read;
unsigned int write;
};
static struct trex_info trex_sci_irps[] = {
{ .pa_base = EXYNOS8895_PA_SCI_IRPS0, },
{ .pa_base = EXYNOS8895_PA_SCI_IRPS1, },
};
static struct trex_info smc_config[] = {
{ .pa_base = EXYNOS8895_PA_SMC0, },
{ .pa_base = EXYNOS8895_PA_SMC1, },
{ .pa_base = EXYNOS8895_PA_SMC2, },
{ .pa_base = EXYNOS8895_PA_SMC3, },
};
static struct trex_info trex_snode[] = {
{ .pa_base = EXYNOS8895_PA_SN_BUSC_M0, .value = 1, },
{ .pa_base = EXYNOS8895_PA_SN_BUSC_M1, .value = 1, },
{ .pa_base = EXYNOS8895_PA_SN_BUSC_M2, .value = 1, },
{ .pa_base = EXYNOS8895_PA_SN_BUSC_M3, .value = 1, },
};
static struct trex_info trex_sci = {
.pa_base = EXYNOS8895_PA_SCI,
};
static struct bts_info exynos_bts[] = {
[BTS_IDX_CP] = {
.name ="cp",
.pa_base = EXYNOS8895_PA_CP,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0xC,
.table[BS_DEFAULT].stat.timeout_en = true,
.table[BS_DEFAULT].stat.timeout = 0x10,
},
[BTS_IDX_DPU0] = {
.name ="dpu0",
.pa_base = EXYNOS8895_PA_DPU0,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x8,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_DPU1] = {
.name ="dpu1",
.pa_base = EXYNOS8895_PA_DPU1,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x8,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_DPU2] = {
.name ="dpu2",
.pa_base = EXYNOS8895_PA_DPU2,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x8,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_CAM0] = {
.name ="cam0",
.pa_base = EXYNOS8895_PA_CAM0,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0xA,
},
[BTS_IDX_CAM1] = {
.name ="cam1",
.pa_base = EXYNOS8895_PA_CAM1,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0xA,
},
[BTS_IDX_ISPLP] = {
.name ="isplp",
.pa_base = EXYNOS8895_PA_ISPLP,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0xA,
},
[BTS_IDX_SRDZ] = {
.name ="srdz",
.pa_base = EXYNOS8895_PA_SRDZ,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.max_rmo = 0x1,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
#ifndef CONFIG_SOC_EMULATOR8895
[BTS_IDX_IVA] = {
.name ="iva",
.pa_base = EXYNOS8895_PA_IVA,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.max_rmo = 0x1,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_DSP] = {
.name ="dsp",
.pa_base = EXYNOS8895_PA_DSP,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.max_rmo = 0x1,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
#endif
[BTS_IDX_VPU] = {
.name ="vpu",
.pa_base = EXYNOS8895_PA_VPU,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.max_rmo = 0x1,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_MFC0] = {
.name ="mfc0",
.pa_base = EXYNOS8895_PA_MFC0,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.max_rmo = 0x1,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_MFC1] = {
.name ="mfc1",
.pa_base = EXYNOS8895_PA_MFC1,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.max_rmo = 0x1,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_G2D0] = {
.name ="g2d0",
.pa_base = EXYNOS8895_PA_G2D0,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.max_rmo = 0x1,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_G2D1] = {
.name ="g2d1",
.pa_base = EXYNOS8895_PA_G2D1,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.max_rmo = 0x1,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_G2D2] = {
.name ="g2d2",
.pa_base = EXYNOS8895_PA_G2D2,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.max_rmo = 0x1,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_FSYS0] = {
.name ="fsys0",
.pa_base = EXYNOS8895_PA_FSYS0,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.rmo = 0x4,
.table[BS_DEFAULT].stat.wmo = 0x4,
.table[BS_DEFAULT].stat.max_rmo = 0x1,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_PDMA] = {
.name ="pdma",
.pa_base = EXYNOS8895_PA_PDMA,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.rmo = 0x4,
.table[BS_DEFAULT].stat.wmo = 0x4,
.table[BS_DEFAULT].stat.max_rmo = 0x1,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_SPDMA] = {
.name ="spdma",
.pa_base = EXYNOS8895_PA_SPDMA,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.rmo = 0x4,
.table[BS_DEFAULT].stat.wmo = 0x4,
.table[BS_DEFAULT].stat.max_rmo = 0x1,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_ABOX] = {
.name ="abox",
.pa_base = EXYNOS8895_PA_ABOX,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0xC,
},
[BTS_IDX_VTS] = {
.name ="vts",
.pa_base = EXYNOS8895_PA_VTS,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.rmo = 0x4,
.table[BS_DEFAULT].stat.wmo = 0x4,
.table[BS_DEFAULT].stat.max_rmo = 0x1,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_FSYS1] = {
.name ="fsys1",
.pa_base = EXYNOS8895_PA_FSYS1,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.rmo = 0x4,
.table[BS_DEFAULT].stat.wmo = 0x4,
.table[BS_DEFAULT].stat.max_rmo = 0x1,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_GNSS] = {
.name ="gnss",
.pa_base = EXYNOS8895_PA_GNSS,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.rmo = 0x4,
.table[BS_DEFAULT].stat.wmo = 0x4,
.table[BS_DEFAULT].stat.max_rmo = 0x1,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_ALIVE] = {
.name ="alive",
.pa_base = EXYNOS8895_PA_ALIVE,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.rmo = 0x4,
.table[BS_DEFAULT].stat.wmo = 0x4,
.table[BS_DEFAULT].stat.max_rmo = 0x1,
.table[BS_DEFAULT].stat.max_wmo = 0x1,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_G3D0] = {
.name ="g3d0",
.pa_base = EXYNOS8895_PA_G3D0,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_G3D1] = {
.name ="g3d1",
.pa_base = EXYNOS8895_PA_G3D1,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_G3D2] = {
.name ="g3d1",
.pa_base = EXYNOS8895_PA_G3D2,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
[BTS_IDX_G3D3] = {
.name ="g3d1",
.pa_base = EXYNOS8895_PA_G3D3,
.type = BT_TREX,
.enable = true,
.table[BS_DEFAULT].stat.priority = 0x4,
.table[BS_DEFAULT].stat.busy_rmo = 0x1,
.table[BS_DEFAULT].stat.busy_wmo = 0x1,
.table[BS_DEFAULT].stat.full_rmo = 0x1,
.table[BS_DEFAULT].stat.full_wmo = 0x1,
},
};
static struct bts_scenario bts_scen[BS_MAX] = {
[BS_DEFAULT] = {
.name = "default",
},
[BS_G3D_PEFORMANCE] = {
.name = "g3d perf",
},
};
static DEFINE_SPINLOCK(bts_lock);
static DEFINE_SPINLOCK(qmax_lock);
static DEFINE_SPINLOCK(tq_lock);
static void bts_set_ip_table(struct bts_info *bts)
{
enum bts_scen_type scen = bts->top_scen;
BTS_DBG("[BTS] %s bts scen: [%s]->[%s]\n", bts->name,
bts_scen[scen].name, bts_scen[scen].name);
switch (bts->type) {
case BT_TREX:
bts_setqos(bts->va_base, &bts->table[scen].stat);
break;
default:
break;
}
}
static void bts_add_scen(enum bts_scen_type scen)
{
struct bts_info *first = bts_scen[scen].head;
struct bts_info *bts = bts_scen[scen].head;
int next = 0;
int prev = 0;
if (!bts)
return;
BTS_DBG("[BTS] scen %s on\n", bts_scen[scen].name);
do {
if (bts->enable &&!bts->table[scen].next_scen) {
if (scen >= bts->top_scen) {
/* insert at top priority */
bts->table[scen].prev_scen = bts->top_scen;
bts->table[bts->top_scen].next_scen = scen;
bts->top_scen = scen;
bts->table[scen].next_scen = -1;
bts_set_ip_table(bts);
} else {
/* insert at middle */
for (prev = bts->top_scen; prev > scen;
prev = bts->table[prev].prev_scen)
next = prev;
bts->table[scen].prev_scen =
bts->table[next].prev_scen;
bts->table[scen].next_scen =
bts->table[prev].next_scen;
bts->table[next].prev_scen = scen;
bts->table[prev].next_scen = scen;
}
}
bts = bts->table[scen].next_bts;
/* set all bts ip in the current scenario */
} while (bts && bts != first);
}
static void bts_del_scen(enum bts_scen_type scen)
{
struct bts_info *first = bts_scen[scen].head;
struct bts_info *bts = bts_scen[scen].head;
int next = 0;
int prev = 0;
if (!bts)
return;
BTS_DBG("[BTS] scen %s off\n", bts_scen[scen].name);
do {
if (bts->enable && bts->table[scen].next_scen) {
if (scen == bts->top_scen) {
/* revert to prev scenario */
prev = bts->table[scen].prev_scen;
bts->top_scen = prev;
bts->table[prev].next_scen = -1;
bts->table[scen].next_scen = 0;
bts->table[scen].prev_scen = 0;
bts_set_ip_table(bts);
} else if (scen < bts->top_scen) {
/* delete mid scenario */
prev = bts->table[scen].prev_scen;
next = bts->table[scen].next_scen;
bts->table[next].prev_scen =
bts->table[scen].prev_scen;
bts->table[prev].next_scen =
bts->table[scen].next_scen;
bts->table[scen].prev_scen = 0;
bts->table[scen].next_scen = 0;
} else {
BTS_DBG("[BTS]%s scenario couldn't \
exist above top_scen\n",
bts_scen[scen].name);
}
}
bts = bts->table[scen].next_bts;
/* revert all bts ip to prev in the current scenario */
} while (bts && bts != first);
}
static unsigned int find_tq(unsigned int freq)
{
int i;
unsigned int ret;
unsigned long flags;
spin_lock_irqsave(&tq_lock, flags);
for (i = 0; i < exynos_ntq - 1 &&
freq <= exynos_tq[i+1]; i += 2)
;
ret = exynos_tq[i];
spin_unlock_irqrestore(&tq_lock, flags);
return ret;
}
static void find_qmax(unsigned int freq, unsigned int *read_mo,
unsigned int *write_mo)
{
int i;
unsigned long flags;
spin_lock_irqsave(&qmax_lock, flags);
for (i = 0; i < exynos_nqmax_r - 1 &&
freq <= exynos_qmax_r[i+1]; i += 2)
;
*read_mo = exynos_qmax_r[i];
for (i = 0; i < exynos_nqmax_w - 1 &&
freq <= exynos_qmax_w[i+1]; i += 2)
;
*write_mo = exynos_qmax_w[i];
spin_unlock_irqrestore(&qmax_lock, flags);
}
static void exynos_bts_mif_update(unsigned int freq)
{
int i;
unsigned int read_mo;
unsigned int write_mo;
unsigned int tq;
find_qmax(freq, &read_mo, &write_mo);
for (i = 0; i < ARRAY_SIZE(trex_snode); i++)
if (trex_snode[i].read != read_mo ||
trex_snode[i].write != write_mo) {
bts_set_qmax(trex_snode[i].va_base,
read_mo * trex_snode[i].value,
write_mo * trex_snode[i].value);
trex_snode[i].read = read_mo;
trex_snode[i].write = write_mo;
}
tq = find_tq(freq);
if (trex_sci.value != tq) {
bts_set_tq(trex_sci.va_base, tq);
trex_sci.value = tq;
}
exynos_mif_freq = freq;
}
void bts_update_scen(enum bts_scen_type scen, unsigned int val)
{
bool on = val ? 1 : 0;
int i;
if (scen <= BS_DEFAULT || scen >= BS_MAX)
return;
switch (scen) {
case BS_MIF_CHANGE:
exynos_bts_mif_update(val);
break;
case BS_MFC_UHD:
case BS_G3D_PEFORMANCE:
break;
case BS_CAMERA_DEFAULT:
if (on) {
exynos_qbusy = 4;
exynos_qfull_low = 0xa;
exynos_qfull_high = 0x10;
} else {
exynos_qbusy = QBUSY_DEFAULT;
exynos_qfull_low = QFULL_LOW_DEFAULT;
exynos_qfull_high = QFULL_HIGH_DEFAULT;
}
bts_set_qfull(trex_sci.va_base, exynos_qfull_low,
exynos_qfull_high);
for (i = 0; i < ARRAY_SIZE(smc_config); i++)
bts_set_qbusy(smc_config[i].va_base, exynos_qbusy);
break;
default:
spin_lock(&bts_lock);
if (on)
bts_add_scen(scen);
else
bts_del_scen(scen);
spin_unlock(&bts_lock);
break;
}
}
static void scen_chaining(enum bts_scen_type scen)
{
struct bts_info *prev = NULL;
struct bts_info *first = NULL;
struct bts_info *bts;
for (bts = exynos_bts;
bts <= &exynos_bts[ARRAY_SIZE(exynos_bts) - 1]; bts++) {
if (bts->table[scen].stat.priority) {
if (!first)
first = bts;
if (prev)
prev->table[scen].next_bts = bts;
prev = bts;
}
}
if (prev)
prev->table[scen].next_bts = first;
bts_scen[scen].head = first;
}
static unsigned int *get_tokenized_data(const char *buf, int *num_tokens)
{
const char *cp;
int i;
int ntokens = 1;
unsigned int *tokenized_data;
int err = -EINVAL;
cp = buf;
while ((cp = strpbrk(cp + 1, " :")))
ntokens++;
if (!(ntokens & 0x1))
goto err;
tokenized_data = kmalloc(ntokens * sizeof(unsigned int), GFP_KERNEL);
if (!tokenized_data) {
err = -ENOMEM;
goto err;
}
cp = buf;
i = 0;
while (i < ntokens) {
if (*cp == '0' && (*(cp + 1) == 'x' || *(cp + 1) == 'X')) {
if (sscanf(cp, "%x", &tokenized_data[i++]) != 1)
goto err_kfree;
} else {
if (sscanf(cp, "%u", &tokenized_data[i++]) != 1)
goto err_kfree;
}
cp = strpbrk(cp, " :");
if (!cp)
break;
cp++;
}
if (i != ntokens)
goto err_kfree;
*num_tokens = ntokens;
return tokenized_data;
err_kfree:
kfree(tokenized_data);
err:
return ERR_PTR(err);
}
static int exynos_qos_status_open_show(struct seq_file *buf, void *d)
{
struct bts_info *bts;
spin_lock(&bts_lock);
for (bts = exynos_bts;
bts <= &exynos_bts[ARRAY_SIZE(exynos_bts) - 1]; bts++) {
if (!bts->enable)
continue;
seq_printf(buf, "%5s(%s): ", bts->name,
bts_scen[bts->top_scen].name);
switch (bts->type) {
case BT_TREX:
bts_showqos(bts->va_base, buf);
break;
default:
seq_puts(buf, "none\n");
}
}
spin_unlock(&bts_lock);
return 0;
}
static int exynos_qos_open(struct inode *inode, struct file *file)
{
return single_open(file, exynos_qos_status_open_show, inode->i_private);
}
static const struct file_operations debug_qos_status_fops = {
.open = exynos_qos_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int exynos_tq_status_open_show(struct seq_file *buf, void *d)
{
int i;
unsigned long flags;
spin_lock_irqsave(&tq_lock, flags);
for (i = 0; i < exynos_ntq; i++) {
seq_printf(buf, "%u%s", exynos_tq[i],
i & 0x1 ? ":" : " ");
}
seq_printf(buf, "\n");
spin_unlock_irqrestore(&tq_lock, flags);
return 0;
}
static int exynos_tq_open(struct inode *inode, struct file *file)
{
return single_open(file, exynos_tq_status_open_show, inode->i_private);
}
static ssize_t exynos_tq_write(struct file *file, const char __user *buf, size_t count,
loff_t *f_pos)
{
int ntokens;
unsigned int *new_tq = NULL;
unsigned long flags;
new_tq = get_tokenized_data(buf, &ntokens);
if (IS_ERR(new_tq))
return PTR_RET(new_tq);
spin_lock_irqsave(&tq_lock, flags);
if (exynos_tq != default_exynos_tq)
kfree(exynos_tq);
exynos_tq = new_tq;
exynos_ntq = ntokens;
spin_unlock_irqrestore(&tq_lock, flags);
exynos_bts_mif_update(exynos_mif_freq);
return count;
}
static const struct file_operations debug_tq_status_fops = {
.open = exynos_tq_open,
.write = exynos_tq_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int exynos_qmax_r_status_open_show(struct seq_file *buf, void *d)
{
int i;
unsigned long flags;
spin_lock_irqsave(&qmax_lock, flags);
for (i = 0; i < exynos_nqmax_r; i++) {
if (i & 0x1)
seq_printf(buf, "%u%s", exynos_qmax_r[i],
":");
else
seq_printf(buf, "0x%x%s", exynos_qmax_r[i],
" ");
}
seq_printf(buf, "\n");
spin_unlock_irqrestore(&qmax_lock, flags);
return 0;
}
static int exynos_qmax_r_open(struct inode *inode, struct file *file)
{
return single_open(file, exynos_qmax_r_status_open_show, inode->i_private);
}
static ssize_t exynos_qmax_r_write(struct file *file, const char __user *buf, size_t count,
loff_t *f_pos)
{
int ntokens;
unsigned int *new_qmax = NULL;
unsigned long flags;
new_qmax = get_tokenized_data(buf, &ntokens);
if (IS_ERR(new_qmax))
return PTR_RET(new_qmax);
spin_lock_irqsave(&qmax_lock, flags);
if (exynos_qmax_r != default_exynos_qmax_r)
kfree(exynos_qmax_r);
exynos_qmax_r = new_qmax;
exynos_nqmax_r = ntokens;
spin_unlock_irqrestore(&qmax_lock, flags);
exynos_bts_mif_update(exynos_mif_freq);
return count;
}
static const struct file_operations debug_qmax_read_status_fops = {
.open = exynos_qmax_r_open,
.write = exynos_qmax_r_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int exynos_qmax_w_status_open_show(struct seq_file *buf, void *d)
{
int i;
unsigned long flags;
spin_lock_irqsave(&qmax_lock, flags);
for (i = 0; i < exynos_nqmax_w; i++) {
if (i & 0x1)
seq_printf(buf, "%u%s", exynos_qmax_w[i],
":");
else
seq_printf(buf, "0x%x%s", exynos_qmax_w[i],
" ");
}
seq_printf(buf, "\n");
spin_unlock_irqrestore(&qmax_lock, flags);
return 0;
}
static int exynos_qmax_w_open(struct inode *inode, struct file *file)
{
return single_open(file, exynos_qmax_w_status_open_show, inode->i_private);
}
static ssize_t exynos_qmax_w_write(struct file *file, const char __user *buf, size_t count,
loff_t *f_pos)
{
int ntokens;
unsigned int *new_qmax = NULL;
unsigned long flags;
new_qmax = get_tokenized_data(buf, &ntokens);
if (IS_ERR(new_qmax))
return PTR_RET(new_qmax);
spin_lock_irqsave(&qmax_lock, flags);
if (exynos_qmax_w != default_exynos_qmax_w)
kfree(exynos_qmax_w);
exynos_qmax_w = new_qmax;
exynos_nqmax_w = ntokens;
spin_unlock_irqrestore(&qmax_lock, flags);
exynos_bts_mif_update(exynos_mif_freq);
return count;
}
static const struct file_operations debug_qmax_write_status_fops = {
.open = exynos_qmax_w_open,
.write = exynos_qmax_w_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int exynos_qbusy_status_open_show(struct seq_file *buf, void *d)
{
seq_printf(buf, "%u\n", exynos_qbusy);
return 0;
}
static int exynos_qbusy_open(struct inode *inode, struct file *file)
{
return single_open(file, exynos_qbusy_status_open_show, inode->i_private);
}
static ssize_t exynos_qbusy_write(struct file *file, const char __user *buf, size_t count,
loff_t *f_pos)
{
int i;
if (sscanf(buf, "%u", &exynos_qbusy) != 1)
return count;
for (i = 0; i < ARRAY_SIZE(smc_config); i++)
bts_set_qbusy(smc_config[i].va_base, exynos_qbusy);
return count;
}
static const struct file_operations debug_qbusy_status_fops = {
.open = exynos_qbusy_open,
.write = exynos_qbusy_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static void bts_debugfs(void)
{
struct dentry *den;
den = debugfs_create_dir("bts", NULL);
debugfs_create_file("qos", 0440, den, NULL, &debug_qos_status_fops);
debugfs_create_file("qmax_read", 0440, den, NULL,
&debug_qmax_read_status_fops);
debugfs_create_file("qmax_write", 0440, den, NULL,
&debug_qmax_write_status_fops);
debugfs_create_file("qbusy", 0440, den, NULL,
&debug_qbusy_status_fops);
debugfs_create_file("tq", 0440, den, NULL, &debug_tq_status_fops);
if (!debugfs_create_u32("log", 0644, den, &exynos_bts_log)) {
pr_err("[BTS]: could't create debugfs bts log\n");
}
if (!debugfs_create_u32("mif_util", 0644, den, &exynos_mif_util)) {
pr_err("[BTS]: could't create debugfs mif util\n");
}
if (!debugfs_create_u32("int_util", 0644, den, &exynos_int_util)) {
pr_err("[BTS]: could't create debugfs int util\n");
}
}
static void bts_initialize_domains(void)
{
unsigned long i;
struct bts_info *bts;
bts_set_qfull(trex_sci.va_base, exynos_qfull_low, exynos_qfull_high);
for (i = 0; i < ARRAY_SIZE(smc_config); i++)
bts_set_qbusy(smc_config[i].va_base, exynos_qbusy);
for (i = 0; i < ARRAY_SIZE(trex_sci_irps); i++)
bts_trex_init(trex_sci_irps[i].va_base);
spin_lock(&bts_lock);
for (bts = exynos_bts;
bts <= &exynos_bts[ARRAY_SIZE(exynos_bts) - 1]; bts++) {
if (!bts->enable)
continue;
bts_set_ip_table(bts);
}
spin_unlock(&bts_lock);
exynos_bts_mif_update(exynos_mif_freq);
}
static int exynos_bts_notifier_event(struct notifier_block *this,
unsigned long event,
void *ptr)
{
switch ((unsigned int)event) {
case PM_POST_SUSPEND:
bts_initialize_domains();
return NOTIFY_OK;
break;
case PM_SUSPEND_PREPARE:
return NOTIFY_OK;
break;
}
return NOTIFY_DONE;
}
static struct notifier_block exynos_bts_notifier = {
.notifier_call = exynos_bts_notifier_event,
};
#define BIT_PER_BYTE 8
static unsigned int bts_bw_calc(struct bts_dpp_info *dpp, unsigned int vclk)
{
unsigned int bw;
unsigned int dst_w, dst_h;
dst_w = dpp->dst.x2 - dpp->dst.x1;
dst_h = dpp->dst.y2 - dpp->dst.y1;
if (!(dst_w && dst_h))
return 0;
/* use multifactor for KB/s */
bw = ((u64)dpp->src_h * dpp->src_w * dpp->bpp * vclk) /
(BIT_PER_BYTE * dst_h * dst_w);
return bw;
}
static unsigned int bts_find_max_bw(struct bts_decon_info *decon,
const struct bts_layer_position *input, int idx)
{
struct bts_layer_position output;
struct bts_dpp_info *dpp;
unsigned int max = 0;
int i;
for (i = idx; i < BTS_DPP_MAX; i++) {
dpp = &decon->dpp[i];
if (!dpp->used)
continue;
output.y1 = input->y1 < dpp->dst.y1 ? dpp->dst.y1 : input->y1;
output.y2 = input->y2 > dpp->dst.y2 ? dpp->dst.y2 : input->y2;
output.x1 = input->x1 < dpp->dst.x1 ? dpp->dst.x1 : input->x1;
output.x2 = input->x2 > dpp->dst.x2 ? dpp->dst.x2 : input->x2;
if (output.y1 < output.y2 && output.x1 < output.x2) {
unsigned int bw;
bw = dpp->bw + bts_find_max_bw(decon, &output, i + 1);
if (bw > max)
max = bw;
}
}
return max;
}
static unsigned int bts_update_decon_bw(struct bts_decon_info *decon)
{
unsigned int max = 0;
struct bts_dpp_info *dpp;
int i;
for (i = 0; i < BTS_DPP_MAX; i++) {
dpp = &decon->dpp[i];
if (!dpp->used)
continue;
dpp->bw = bts_bw_calc(&decon->dpp[i], decon->vclk);
}
for (i = 0; i < BTS_DPP_MAX; i++) {
unsigned int bw;
dpp = &decon->dpp[i];
if (!dpp->used)
continue;
bw = dpp->bw + bts_find_max_bw(decon, &dpp->dst, i + 1);
if (bw > max)
max = bw;
}
return max;
}
unsigned int bts_calc_bw(enum bts_bw_type type, void *data)
{
unsigned int bw;
switch (type) {
case BTS_BW_DECON0:
case BTS_BW_DECON1:
case BTS_BW_DECON2:
bw = bts_update_decon_bw(data);
break;
default:
bw = 0;
break;
}
return bw;
}
void bts_update_bw(enum bts_bw_type type, unsigned int bw)
{
static unsigned int bts_bw[BTS_BW_MAX];
unsigned int mif_freq;
unsigned int int_freq;
unsigned int total_bw = 0;
unsigned int bw_r = 0;
unsigned int bw_w = 0;
int i;
if (type >= BTS_BW_MAX)
return;
mutex_lock(&media_mutex);
if (bw == bts_bw[type]) {
mutex_unlock(&media_mutex);
return;
}
bts_bw[type] = bw;
for (i = 0; i < BTS_BW_MAX; i++) {
if (i < BTS_BW_W)
bw_r += bts_bw[i];
else
bw_w += bts_bw[i];
total_bw += bts_bw[i];
}
/* MIF minimum frequency calculation as per BTS guide */
mif_freq = total_bw * 100 / BUS_WIDTH / exynos_mif_util;
int_freq = (bw_w < bw_r ? bw_r : bw_w) * 100 / BUS_WIDTH / exynos_int_util;
pm_qos_update_request(&exynos_mif_bts_qos, mif_freq);
pm_qos_update_request(&exynos_int_bts_qos, int_freq);
BTS_DBG("[BTS] BW(KB/s): bw%i %u total %u, read %u, write %u, "
"freq(Khz): mif %u, int %u\n",
type, bw, total_bw, bw_r, bw_w, mif_freq, int_freq);
mutex_unlock(&media_mutex);
}
static int __init exynos_bts_init(void)
{
int i;
struct bts_info *bts;
for (bts = exynos_bts;
bts <= &exynos_bts[ARRAY_SIZE(exynos_bts) - 1]; bts++) {
bts->va_base = ioremap(bts->pa_base, SZ_2K);
if (!bts->va_base) {
pr_err("failed to map bts physical address\n");
bts->enable = false;
}
}
for (i = 0; i < ARRAY_SIZE(trex_sci_irps); i++) {
trex_sci_irps[i].va_base = ioremap(trex_sci_irps[i].pa_base,
SZ_4K);
if (!trex_sci_irps[i].va_base) {
pr_err("failed to map trex irp-s physical address\n");
}
}
for (i = 0; i < ARRAY_SIZE(smc_config); i++) {
smc_config[i].va_base = ioremap(smc_config[i].pa_base,
SZ_4K);
if (!smc_config[i].va_base) {
pr_err("failed to map smc physical address\n");
}
}
for (i = 0; i < ARRAY_SIZE(trex_snode); i++) {
trex_snode[i].va_base = ioremap(trex_snode[i].pa_base,
SZ_1K);
if (!trex_snode[i].va_base) {
pr_err("failed to map trex snode physical address\n");
}
}
trex_sci.va_base = ioremap(trex_sci.pa_base,
SZ_1K);
if (!trex_sci.va_base) {
pr_err("failed to map sci physical address\n");
}
for (i = BS_DEFAULT + 1; i < BS_MAX; i++) {
scen_chaining(i);
}
exynos_tq = default_exynos_tq;
exynos_ntq = ARRAY_SIZE(default_exynos_tq);
exynos_qmax_r = default_exynos_qmax_r;
exynos_nqmax_r = ARRAY_SIZE(default_exynos_qmax_r);
exynos_qmax_w = default_exynos_qmax_w;
exynos_nqmax_w = ARRAY_SIZE(default_exynos_qmax_w);
bts_initialize_domains();
pm_qos_add_request(&exynos_mif_bts_qos, PM_QOS_BUS_THROUGHPUT, 0);
pm_qos_add_request(&exynos_int_bts_qos, PM_QOS_DEVICE_THROUGHPUT, 0);
register_pm_notifier(&exynos_bts_notifier);
bts_debugfs();
pr_info("BTS: driver is initialized\n");
return 0;
}
arch_initcall(exynos_bts_init);