kernel_samsung_a53x/drivers/bts/exynos-bts.c
2024-06-15 16:02:09 -03:00

1564 lines
40 KiB
C
Executable file

/*
* Copyright (c) 2018 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 publised
* by the Free Software Foundation.
*
* BTS Bus Traffic Shaper device driver
*
*/
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/debugfs.h>
#include <linux/syscore_ops.h>
#include <linux/suspend.h>
#include <soc/samsung/exynos_pm_qos.h>
#include <soc/samsung/cal-if.h>
#include <dt-bindings/soc/samsung/exynos-bts.h>
#include "bts.h"
#define BTS_PDEV_NAME "exynos-bts"
#define ID_DEFAULT 0
#define BTSDBG_LOG(x...) if (btsdbg_log) dev_notice(x)
static bool btsdbg_log = false;
static struct exynos_pm_qos_request exynos_mif_qos;
static struct exynos_pm_qos_request exynos_int_qos;
static struct bts_device *btsdev;
static void bts_calc_bw(void)
{
unsigned int i = 0;
unsigned int total_read = 0;
unsigned int total_write = 0;
unsigned int mif_freq, int_freq;
struct bts_bw_params *bw_params = &btsdev->bw_params;
mutex_lock(&btsdev->mutex_lock);
btsdev->peak_bw = 0;
btsdev->total_bw = 0;
for (i = 0; i < btsdev->num_bts; i++) {
if (btsdev->peak_bw < btsdev->bts_bw[i].peak)
btsdev->peak_bw = btsdev->bts_bw[i].peak;
total_read += btsdev->bts_bw[i].read;
total_write += btsdev->bts_bw[i].write;
}
btsdev->total_bw = total_read + total_write;
if (btsdev->peak_bw < (total_read / bw_params->num_channel))
btsdev->peak_bw = (total_read / bw_params->num_channel);
if (btsdev->peak_bw < (total_write / bw_params->num_channel))
btsdev->peak_bw = (total_write / bw_params->num_channel);
mif_freq = (btsdev->total_bw * 100) / bw_params->mif_bus_width / bw_params->mif_util;
int_freq = (btsdev->peak_bw * 100) / bw_params->bus_width / bw_params->int_util;
BTSDBG_LOG(btsdev->dev, "BW: T:%.8u R:%.8u W:%.8u P:%.8u MIF:%.8u INT:%.8u\n",
btsdev->total_bw, total_read, total_write, btsdev->peak_bw, mif_freq, int_freq);
#if defined(CONFIG_EXYNOS_PM_QOS) || defined(CONFIG_EXYNOS_PM_QOS_MODULE)
exynos_pm_qos_update_request(&exynos_mif_qos, mif_freq);
exynos_pm_qos_update_request(&exynos_int_qos, int_freq);
#endif
mutex_unlock(&btsdev->mutex_lock);
}
static void bts_set(unsigned int scen, unsigned int index)
{
struct bts_info *info = btsdev->bts_list;
int ret = 0;
if (info[index].ops->set_bts != NULL) {
if (info[index].pd_on && info[index].status) {
/* Check scenario set exists */
if (info[index].stat[scen].stat_on) {
ret = info[index].ops->set_bts(info[index].va_base, &(info[index].stat[scen]));
} else{
ret = info[index].ops->set_bts(info[index].va_base, &(info[index].stat[ID_DEFAULT]));
}
}
if (ret)
dev_err(btsdev->dev, "%s failed! (scenario=%u) (index=%u)\n",
__func__, scen, index);
}
}
void bts_pd_sync(unsigned int cal_id, int on)
{
struct bts_info *info = btsdev->bts_list;
unsigned int i;
if (info == NULL)
return;
spin_lock(&btsdev->lock);
for (i = 0; i < btsdev->num_bts; i++) {
if (!info[i].pd_id)
continue;
if (info[i].pd_id == cal_id) {
info[i].pd_on = on ? true : false;
if (on)
bts_set(btsdev->top_scen, i);
}
}
spin_unlock(&btsdev->lock);
}
int bts_get_bwindex(const char *name)
{
struct bts_bw *bw = btsdev->bts_bw;
unsigned int index = 0;
int ret = 0;
spin_lock(&btsdev->lock);
for (index = 0; (bw[index].name != NULL) && (index < btsdev->num_bts); index++) {
if (!strcmp(bw[index].name, name)) {
ret = index;
goto out;
}
}
if (index == btsdev->num_bts) {
ret = -EINVAL;
goto out;
} else {
bw[index].name = devm_kstrdup(btsdev->dev, name, GFP_ATOMIC);
if (bw[index].name == NULL) {
dev_err(btsdev->dev, "failed to allocate bandwidth name\n");
ret = -ENOMEM;
goto out;
}
ret = index;
}
out:
spin_unlock(&btsdev->lock);
return ret;
}
EXPORT_SYMBOL(bts_get_bwindex);
unsigned int bts_get_scenindex(const char *name)
{
unsigned int index;
struct bts_scen *scen = btsdev->scen_list;
spin_lock(&btsdev->lock);
for (index = 1; index < btsdev->num_scen; index++)
if (strcmp(name, scen[index].name) == 0)
break;
if (index == btsdev->num_scen) {
spin_unlock(&btsdev->lock);
return 0;
}
spin_unlock(&btsdev->lock);
return index;
}
EXPORT_SYMBOL(bts_get_scenindex);
int bts_update_bw(unsigned int index, struct bts_bw bw)
{
if (index >= btsdev->num_bts) {
dev_err(btsdev->dev, "Invalid index! Should be smaller than %u(index=%u)\n",
btsdev->num_bts, index);
return -EINVAL;
}
spin_lock(&btsdev->lock);
btsdev->bts_bw[index].peak = bw.peak;
btsdev->bts_bw[index].read = bw.read;
btsdev->bts_bw[index].write = bw.write;
spin_unlock(&btsdev->lock);
bts_calc_bw();
return 0;
}
EXPORT_SYMBOL(bts_update_bw);
int bts_add_scenario(unsigned int index)
{
struct bts_scen *scen = btsdev->scen_list;
unsigned int i = 0;
spin_lock(&btsdev->lock);
if (index >= btsdev->num_scen) {
dev_err(btsdev->dev, "Invalid scenario index!\n");
spin_unlock(&btsdev->lock);
return -EINVAL;
}
if (index == ID_DEFAULT && scen[index].usage_count != 0) {
dev_notice(btsdev->dev, "Default scenario cannot register additional!\n");
spin_unlock(&btsdev->lock);
return 0;
}
scen[index].usage_count++;
if (scen[index].usage_count == 1) {
list_add(&scen[index].node, &btsdev->scen_node);
scen[index].status = true;
if (index >= btsdev->top_scen) {
btsdev->top_scen = index;
for (i = 0; i < btsdev->num_bts; i++)
bts_set(btsdev->top_scen, i);
}
}
spin_unlock(&btsdev->lock);
return 0;
}
EXPORT_SYMBOL(bts_add_scenario);
int bts_del_scenario(unsigned int index)
{
struct bts_scen *scen = btsdev->scen_list;
unsigned int i = 0;
spin_lock(&btsdev->lock);
/* CAUTION: Default scenario cannot be deleted! */
if (index >= btsdev->num_scen) {
dev_err(btsdev->dev, "Invalid scenario index!\n");
spin_unlock(&btsdev->lock);
return -EINVAL;
}
if (index == ID_DEFAULT) {
dev_notice(btsdev->dev, "Default scenario cannot be deleted!\n");
spin_unlock(&btsdev->lock);
return 0;
}
scen[index].usage_count--;
if (scen[index].usage_count < 0) {
dev_warn(btsdev->dev, "Usage count is below 0!\n");
scen[index].usage_count = 0;
spin_unlock(&btsdev->lock);
return 0;
}
if (scen[index].usage_count == 0) {
list_del(&scen[index].node);
scen[index].status = false;
if (index == btsdev->top_scen) {
btsdev->top_scen = ID_DEFAULT;
list_for_each_entry(scen, &btsdev->scen_node, node)
if (scen->index >= btsdev->top_scen)
btsdev->top_scen = scen->index;
for (i = 0; i < btsdev->num_bts; i++)
bts_set(btsdev->top_scen, i);
}
}
spin_unlock(&btsdev->lock);
return 0;
}
EXPORT_SYMBOL(bts_del_scenario);
int bts_change_mo(unsigned int scen, unsigned int ip,
unsigned int rmo, unsigned int wmo)
{
struct bts_info *info = btsdev->bts_list;
struct bts_stat *stat;
unsigned int old_rmo = 0;
unsigned int old_wmo = 0;
int ret = 0;
if (ip >= btsdev->num_bts) {
pr_err("%s: IP ip should be in range of (0 ~ %d). input=(%d)\n",
__func__, btsdev->num_bts - 1, ip);
return -EINVAL;
}
if (scen >= btsdev->num_scen) {
pr_err("%s: SCEN ip should be in range of (0 ~ %d). input=(%d)\n",
__func__, btsdev->num_scen - 1, scen);
return -EINVAL;
}
stat = info[ip].stat;
spin_lock(&btsdev->lock);
if (stat[scen].rmo == rmo && stat[scen].wmo == wmo) {
pr_info("%s: requested same mo: rmo: %d, wmo: %d\n",
__func__, rmo, wmo);
goto out;
}
stat[scen].stat_on = true;
old_rmo = stat[scen].rmo;
old_wmo = stat[scen].wmo;
stat[scen].rmo = rmo;
stat[scen].wmo = wmo;
if (scen != btsdev->top_scen)
goto out;
if (info[ip].ops->set_mo != NULL) {
if (info[ip].pd_on) {
if (info[ip].ops->set_mo(info[ip].va_base, &stat[scen]))
pr_warn("%s: set_mo failed. input=(%d)\n",
__func__, ip);
}
}
pr_info("%s: scenario:%d, ip: %d, rmo: %d -> %d, wmo: %d -> %d\n",
__func__, scen, ip, old_rmo, rmo, old_wmo, wmo);
out:
spin_unlock(&btsdev->lock);
return ret;
}
EXPORT_SYMBOL(bts_change_mo);
/* DebugFS for BTS */
static int exynos_bts_hwstatus_open_show(struct seq_file *buf, void *d)
{
struct bts_info info;
struct bts_stat stat;
int ret, i = 0;
/* Check BTS setting */
for (i = 0; i < btsdev->num_bts; i++) {
info = btsdev->bts_list[i];
if (!info.va_base)
continue;
if (!info.pd_on)
continue;
if (info.type == SCI_BTS)
stat = *(info.stat);
if (info.ops->get_bts != NULL && info.type == IP_BTS) {
spin_lock(&btsdev->lock);
ret = info.ops->get_bts(info.va_base, &stat);
spin_unlock(&btsdev->lock);
if (!ret) {
seq_printf(buf, "%s:\tARQOS 0x%X, AWQOS 0x%X, RMO 0x%.4X, WMO 0x%.4X, "\
"QUR(0x%X) TH_R 0x%.2X, TH_W 0x%.2X, ",
info.name, stat.arqos, stat.awqos, stat.rmo, stat.wmo,
stat.qurgent_on, stat.qurgent_th_r, stat.qurgent_th_w);
if (stat.blocking_on)
seq_printf(buf, "BLK(1) FR 0x%.4X, FW 0x%.4X, BR 0x%.4X, BW 0x%.4X, "\
"MAX0_R 0x%.4X, MAX0_W 0x%.4X, MAX1_R 0x%.4X, MAX1_W 0x%.4X\n",
stat.qfull_limit_r, stat.qfull_limit_w, stat.qbusy_limit_r, stat.qbusy_limit_w,
stat.qmax0_limit_r, stat.qmax0_limit_w, stat.qmax1_limit_r, stat.qmax1_limit_w);
else
seq_printf(buf, "BLK(0)\n");
}
}
}
return 0;
}
static int exynos_bts_hwstatus_open(struct inode *inode, struct file *file)
{
return single_open(file, exynos_bts_hwstatus_open_show, inode->i_private);
}
static int exynos_bts_scenario_open_show(struct seq_file *buf, void *d)
{
struct bts_scen *scen;
unsigned int i = 0;
seq_printf(buf, "Current Top scenario: [%u]%s\n ",
btsdev->top_scen,
btsdev->scen_list[btsdev->top_scen].name);
list_for_each_entry(scen, &(btsdev->scen_node), node)
seq_printf(buf, "%u - ", scen->index);
seq_printf(buf, "\n");
for (i = 0; i < btsdev->num_scen; i++)
seq_printf(buf, "bts scen[%u] %s(%d) - status: %s\n",
btsdev->scen_list[i].index,
btsdev->scen_list[i].name,
btsdev->scen_list[i].usage_count,
(btsdev->scen_list[i].status ? "on" : "off"));
return 0;
}
static int exynos_bts_scenario_open(struct inode *inode, struct file *file)
{
return single_open(file, exynos_bts_scenario_open_show, inode->i_private);
}
static ssize_t exynos_bts_scenario_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
char buf[32];
ssize_t buf_size;
int ret, scen, on;
buf_size = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count);
if (buf_size < 0)
return buf_size;
buf[buf_size] = '\0';
ret = sscanf(buf, "%d %d\n", &scen, &on);
if (ret != 2) {
pr_err("%s: sscanf failed. Invalid input variables. count=(%d)\n",
__func__, ret);
return -EINVAL;
}
if (scen >= btsdev->num_scen) {
pr_err("%s: Index should be in range of (0 ~ %d). input=(%d)\n",
__func__, btsdev->num_scen - 1, scen);
return -EINVAL;
}
if (on == 1)
bts_add_scenario(scen);
else if (on == 0)
bts_del_scenario(scen);
return buf_size;
}
static int exynos_bts_qos_open_show(struct seq_file *buf, void *d)
{
struct bts_info *info = btsdev->bts_list;
struct bts_stat stat;
int ret, i = 0;
for (i = 0; i < btsdev->num_bts; i++) {
if (info[i].ops->get_qos == NULL)
continue;
spin_lock(&btsdev->lock);
if (info[i].pd_on) {
if (info[i].type == SCI_BTS)
stat = *(info[i].stat);
ret = info[i].ops->get_qos(info[i].va_base, &stat);
if (!stat.bypass)
seq_printf(buf, "[%d] %s: \tAR 0x%.1X AW 0x%.1X\n",
i, info[i].name, stat.arqos, stat.awqos);
} else {
seq_printf(buf, "[%d] %s: \tLocal power off!\n",
i, info[i].name);
}
spin_unlock(&btsdev->lock);
}
return 0;
}
static int exynos_bts_qos_open(struct inode *inode, struct file *file)
{
return single_open(file, exynos_bts_qos_open_show, inode->i_private);
}
static ssize_t exynos_bts_qos_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
char buf[64];
ssize_t buf_size;
struct bts_info *info = btsdev->bts_list;
struct bts_stat *stat;
int ret, scen, index, bypass, ar, aw;
buf_size = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count);
if (buf_size < 0)
return buf_size;
buf[buf_size] = '\0';
ret = sscanf(buf, "%d %d %d %d %d\n", &scen, &index, &bypass, &ar, &aw);
if (ret != 5) {
pr_err("%s: sscanf failed. We need 5 inputs. <SCEN IP BYPASS(0/1) ARQOS AWQOS> count=(%d)\n",
__func__, ret);
return -EINVAL;
}
if (index >= btsdev->num_bts) {
pr_err("%s: IP index should be in range of (0 ~ %d). input=(%d)\n",
__func__, btsdev->num_bts - 1, index);
return -EINVAL;
}
if (info[index].type == SCI_BTS)
stat = info[index].stat;
if (scen >= btsdev->num_scen) {
pr_err("%s: SCEN index should be in range of (0 ~ %d). input=(%d)\n",
__func__, btsdev->num_scen - 1, scen);
return -EINVAL;
}
stat = info[index].stat;
if (stat == NULL) {
pr_err("%s: stat is NULL\n", __func__);
return -ENOMEM;
}
spin_lock(&btsdev->lock);
stat[scen].stat_on = true;
if (bypass == 0)
stat[scen].bypass = false;
else if (bypass == 1)
stat[scen].bypass = true;
stat[scen].arqos = ar;
stat[scen].awqos = aw;
if (scen != btsdev->top_scen)
goto out;
if (info[index].ops->set_qos != NULL) {
if (info[index].pd_on) {
if (info[index].ops->set_qos(info[index].va_base, &stat[scen]))
pr_warn("%s: set_qos failed. input=(%d) err=(%d)\n",
__func__, index, ret);
}
}
out:
spin_unlock(&btsdev->lock);
return buf_size;
}
static int exynos_bts_mo_open_show(struct seq_file *buf, void *d)
{
struct bts_info *info = btsdev->bts_list;
struct bts_stat stat;
int ret, i = 0;
for (i = 0; i < btsdev->num_bts; i++) {
if (info[i].ops->get_mo == NULL)
continue;
spin_lock(&btsdev->lock);
if (info[i].pd_on) {
ret = info[i].ops->get_mo(info[i].va_base, &stat);
seq_printf(buf, "[%d] %s: \tRMO 0x%.4X WMO 0x%.4X\n",
i, info[i].name, stat.rmo, stat.wmo);
} else {
seq_printf(buf, "[%d] %s: \tLocal power off!\n",
i, info[i].name);
}
spin_unlock(&btsdev->lock);
}
return 0;
}
static int exynos_bts_mo_open(struct inode *inode, struct file *file)
{
return single_open(file, exynos_bts_mo_open_show, inode->i_private);
}
static ssize_t exynos_bts_mo_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
char buf[64];
ssize_t buf_size;
struct bts_info *info = btsdev->bts_list;
struct bts_stat *stat;
int ret, scen, index, rmo, wmo;
buf_size = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count);
if (buf_size < 0)
return buf_size;
buf[buf_size] = '\0';
ret = sscanf(buf, "%d %d %d %d\n", &scen, &index, &rmo, &wmo);
if (ret != 4) {
pr_err("%s: sscanf failed. We need 4 inputs. <SCEN IP RMO WMO> count=(%d)\n",
__func__, ret);
return -EINVAL;
}
if (index >= btsdev->num_bts) {
pr_err("%s: IP index should be in range of (0 ~ %d). input=(%d)\n",
__func__, btsdev->num_bts - 1, index);
return -EINVAL;
}
if (scen >= btsdev->num_scen) {
pr_err("%s: SCEN index should be in range of (0 ~ %d). input=(%d)\n",
__func__, btsdev->num_scen - 1, scen);
return -EINVAL;
}
stat = info[index].stat;
if (stat == NULL) {
pr_err("%s: stat is NULL\n", __func__);
return -ENOMEM;
}
spin_lock(&btsdev->lock);
stat[scen].stat_on = true;
stat[scen].rmo = rmo;
stat[scen].wmo = wmo;
if (scen != btsdev->top_scen)
goto out;
if (info[index].ops->set_mo != NULL) {
if (info[index].pd_on) {
if (info[index].ops->set_mo(info[index].va_base, &stat[scen]))
pr_warn("%s: set_mo failed. input=(%d) err=(%d)\n",
__func__, index, ret);
}
}
out:
spin_unlock(&btsdev->lock);
return buf_size;
}
static int exynos_bts_urgent_open_show(struct seq_file *buf, void *d)
{
struct bts_info *info = btsdev->bts_list;
struct bts_stat stat;
int ret, i = 0;
for (i = 0; i < btsdev->num_bts; i++) {
if (info[i].ops->get_urgent == NULL)
continue;
spin_lock(&btsdev->lock);
if (info[i].pd_on) {
ret = info[i].ops->get_urgent(info[i].va_base, &stat);
seq_printf(buf, "[%d] %s: \tQUR(0x%X) TH_R 0x%.2X TH_W 0x%.2X\n",
i, info[i].name, stat.qurgent_on, stat.qurgent_th_r, stat.qurgent_th_w);
} else {
seq_printf(buf, "[%d] %s: \tLocal power off!\n",
i, info[i].name);
}
spin_unlock(&btsdev->lock);
}
return 0;
}
static int exynos_bts_urgent_open(struct inode *inode, struct file *file)
{
return single_open(file, exynos_bts_urgent_open_show, inode->i_private);
}
static ssize_t exynos_bts_urgent_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
char buf[64];
ssize_t buf_size;
struct bts_info *info = btsdev->bts_list;
struct bts_stat *stat;
int ret, scen, index, on, th_r, th_w;
buf_size = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count);
if (buf_size < 0)
return buf_size;
buf[buf_size] = '\0';
ret = sscanf(buf, "%d %d %d %d %d\n", &scen, &index, &on, &th_r, &th_w);
if (ret != 5) {
pr_err("%s: sscanf failed. We need 5 inputs. <SCEN IP ON/OFF TH_R TH_W> count=(%d)\n",
__func__, ret);
return -EINVAL;
}
if (index >= btsdev->num_bts) {
pr_err("%s: IP index should be in range of (0 ~ %d). input=(%d)\n",
__func__, btsdev->num_bts - 1, index);
return -EINVAL;
}
if (scen >= btsdev->num_scen) {
pr_err("%s: SCEN index should be in range of (0 ~ %d). input=(%d)\n",
__func__, btsdev->num_scen - 1, scen);
return -EINVAL;
}
stat = info[index].stat;
if (stat == NULL) {
pr_err("%s: stat is NULL\n", __func__);
return -ENOMEM;
}
spin_lock(&btsdev->lock);
stat[scen].stat_on = true;
stat[scen].qurgent_on = on;
stat[scen].qurgent_th_r = th_r;
stat[scen].qurgent_th_w = th_w;
if (scen != btsdev->top_scen)
goto out;
if (info[index].ops->set_urgent != NULL) {
if (info[index].pd_on) {
if (info[index].ops->set_urgent(info[index].va_base, &stat[scen]))
pr_warn("%s: set_urgent failed. input=(%d) err=(%d)\n",
__func__, index, ret);
}
}
out:
spin_unlock(&btsdev->lock);
return buf_size;
}
static int exynos_bts_blocking_open_show(struct seq_file *buf, void *d)
{
struct bts_info *info = btsdev->bts_list;
struct bts_stat stat;
int ret, i = 0;
for (i = 0; i < btsdev->num_bts; i++) {
if (info[i].ops->get_blocking == NULL)
continue;
spin_lock(&btsdev->lock);
if (info[i].pd_on) {
ret = info[i].ops->get_blocking(info[i].va_base, &stat);
if (stat.blocking_on)
seq_printf(buf, "[%d] %s: \tBLK(1) FR 0x%.4X, FW 0x%.4X, BR 0x%.4X, BW 0x%.4X, "\
"MAX0_R 0x%.4X, MAX0_W 0x%.4X, MAX1_R 0x%.4X, MAX1_W 0x%.4X\n",
i, info[i].name,
stat.qfull_limit_r, stat.qfull_limit_w, stat.qbusy_limit_r, stat.qbusy_limit_w,
stat.qmax0_limit_r, stat.qmax0_limit_w, stat.qmax1_limit_r, stat.qmax1_limit_w);
else
seq_printf(buf, "[%d] %s: \tBLK(0)\n",
i, info[i].name);
} else {
seq_printf(buf, "[%d] %s: \tLocal power off!\n",
i, info[i].name);
}
spin_unlock(&btsdev->lock);
}
return 0;
}
static int exynos_bts_blocking_open(struct inode *inode, struct file *file)
{
return single_open(file, exynos_bts_blocking_open_show, inode->i_private);
}
static ssize_t exynos_bts_blocking_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
char buf[64];
ssize_t buf_size;
struct bts_info *info = btsdev->bts_list;
struct bts_stat *stat;
int ret;
int scen, index, on, full_r, full_w, busy_r, busy_w, max0_r, max0_w, max1_r, max1_w;
buf_size = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count);
if (buf_size < 0)
return buf_size;
buf[buf_size] = '\0';
ret = sscanf(buf, "%d %d %d %d %d %d %d %d %d %d %d\n",
&scen, &index, &on, &full_r, &full_w, &busy_r, &busy_w,
&max0_r, &max0_w, &max1_r, &max1_w);
if (ret != 11) {
pr_err("%s: sscanf failed. We need 11 inputs. <SCEN IP ON/OFF FULL_R, FULL_W, BUSY_R, BUSY_W, "\
"MAX0_R, MAX0_W, MAX1_R, MAX1_W count=(%d)\n", __func__, ret);
return -EINVAL;
}
if (index >= btsdev->num_bts) {
pr_err("%s: IP index should be in range of (0 ~ %d). input=(%d)\n",
__func__, btsdev->num_bts - 1, index);
return -EINVAL;
}
if (scen >= btsdev->num_scen) {
pr_err("%s: SCEN index should be in range of (0 ~ %d). input=(%d)\n",
__func__, btsdev->num_scen - 1, scen);
return -EINVAL;
}
stat = info[index].stat;
if (stat == NULL) {
pr_err("%s: stat is NULL\n", __func__);
return -ENOMEM;
}
spin_lock(&btsdev->lock);
stat[scen].stat_on = true;
stat[scen].blocking_on = on;
stat[scen].qfull_limit_r = full_r;
stat[scen].qfull_limit_w = full_w;
stat[scen].qbusy_limit_r = busy_r;
stat[scen].qbusy_limit_w = busy_w;
stat[scen].qmax0_limit_r = max0_r;
stat[scen].qmax0_limit_w = max0_w;
stat[scen].qmax1_limit_r = max1_r;
stat[scen].qmax1_limit_w = max1_w;
if (scen != btsdev->top_scen)
goto out;
if (info[index].ops->set_blocking != NULL) {
if (info[index].pd_on) {
if (info[index].ops->set_blocking(info[index].va_base, &stat[scen]))
pr_warn("%s: set_blocking failed. input=(%d) err=(%d)\n",
__func__, index, ret);
}
}
out:
spin_unlock(&btsdev->lock);
return buf_size;
}
static int exynos_bts_log_open_show(struct seq_file *buf, void *d)
{
seq_printf(buf, "BTSDBG_LOG STATUS: %s\n", (btsdbg_log ? "on" : "off"));
return 0;
}
static int exynos_bts_log_open(struct inode *inode, struct file *file)
{
return single_open(file, exynos_bts_log_open_show, inode->i_private);
}
static ssize_t exynos_bts_log_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
char buf[64];
ssize_t buf_size;
int ret, status;
buf_size = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count);
if (buf_size < 0)
return buf_size;
buf[buf_size] = '\0';
ret = sscanf(buf, "%d\n", &status);
if (ret != 1) {
pr_err("%s: sscanf failed. We need 1 input. <ON> count=(%d)\n",
__func__, ret);
return -EINVAL;
}
if (status == 1)
btsdbg_log = true;
else if (status == 0)
btsdbg_log = false;
return buf_size;
}
static int exynos_bts_drex_open_show(struct seq_file *buf, void *d)
{
struct bts_info *info = btsdev->bts_list;
struct bts_stat stat;
int ret, i = 0, idx;
struct drex_stat drex;
struct drex_pf_stat drex_pf;
stat.drex = &drex;
stat.drex_pf = &drex_pf;
for (i = 0; i < btsdev->num_bts; i++) {
if (info[i].ops->get_bts == NULL || info[i].type != DREX_BTS)
continue;
spin_lock(&btsdev->lock);
if (info[i].pd_on) {
if (info[i].stat[ID_DEFAULT].drex_on) {
stat.drex_on = 1;
stat.drex_pf_on = 0;
memset(stat.drex, 0, sizeof(struct drex_stat));
ret = info[i].ops->get_bts(info[i].va_base, &stat);
if (ret) {
pr_err("%s: failed get bts drex\n", __func__);
goto err_get_bts;
}
seq_printf(buf, "[%d] %s:\n \t(offset: %u) write_config_0\t0x%.8X\n",
i, info[i].name, offsetof(struct drex_stat, write_flush_config_0),
drex.write_flush_config_0);
seq_printf(buf, "\t(offset: %u) write_config_1\t0x%.8X\n",
offsetof(struct drex_stat, write_flush_config_1), drex.brb_cutoff_con);
seq_printf(buf, "\tdrex_timeout 0~15\n");
for (idx = 0; idx < MAX_QOS + 1; idx++)
seq_printf(buf, "\t\t(offset: %u) 0x%.8X\n",
offsetof(struct drex_stat, drex_timeout[idx]),
drex.drex_timeout[idx]);
seq_printf(buf, "\n\tvc_timer_th 0~7\n");
for (idx = 0; idx < VC_TIMER_TH_NR; idx++)
seq_printf(buf, "\t\t(offset: %u) 0x%.8X\n",
offsetof(struct drex_stat, vc_timer_th[idx]),
drex.vc_timer_th[idx]);
seq_printf(buf, "\n\t(offset: %u) cutoff_con\t0x%.8X\n",
offsetof(struct drex_stat, cutoff_con), drex.cutoff_con);
seq_printf(buf, "\t(offset: %u) brb_cutoff_con\t0x%.8X\n",
offsetof(struct drex_stat, brb_cutoff_con), drex.brb_cutoff_con);
seq_printf(buf, "\t(offset: %u) wdbuf_cutoff_con\t0x%.8X\n\n",
offsetof(struct drex_stat, wdbuf_cutoff_con), drex.wdbuf_cutoff_con);
}
if (info[i].stat[ID_DEFAULT].drex_pf_on) {
stat.drex_on = 0;
stat.drex_pf_on = 1;
memset(stat.drex_pf, 0, sizeof(struct drex_pf_stat));
ret = info[i].ops->get_bts(info[i].va_base, &stat);
if (ret) {
pr_err("%s: failed get bts drex_pf\n", __func__);
goto err_get_bts;
}
seq_printf(buf, "[%d] %s:\n", i, info[i].name);
seq_printf(buf, "\t(offset: %u) pf_token_control\t0x%.8X\n",
offsetof(struct drex_pf_stat, pf_token_control), drex_pf.pf_token_control);
seq_printf(buf, "\t(offset: %u) pf_token_threshold0\t0x%.8X\n",
offsetof(struct drex_pf_stat, pf_token_threshold0), drex_pf.pf_token_threshold0);
seq_printf(buf, "\t(offset: %u) pf_rreq_thrt_con\t0x%.8X\n",
offsetof(struct drex_pf_stat, pf_rreq_thrt_con), drex_pf.pf_rreq_thrt_con);
seq_printf(buf, "\t(offset: %u) pf_rreq_thrt_mo_p2 \t0x%.8X\n",
offsetof(struct drex_pf_stat, pf_rreq_thrt_mo_p2), drex_pf.pf_rreq_thrt_mo_p2);
seq_printf(buf, "\tpf_qos_timer 0~7\n");
for (idx = 0; idx < PF_TIMER_NR; idx++)
seq_printf(buf, "\t\t(offset: %u) 0x%.8X\n ", offsetof(struct drex_pf_stat, pf_qos_timer[idx]),
drex_pf.pf_qos_timer[idx]);
seq_printf(buf, "\n");
}
} else {
seq_printf(buf, "[%d] %s: \tLocal power off!\n",
i, info[i].name);
}
err_get_bts:
spin_unlock(&btsdev->lock);
}
seq_printf(buf, "We need 5 inputs. <SCEN DREX_TYPE IP OFFSET VALUE>\n\n");
return 0;
}
static int exynos_bts_drex_open(struct inode *inode, struct file *file)
{
return single_open(file, exynos_bts_drex_open_show, inode->i_private);
}
static ssize_t exynos_bts_drex_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
char buf[64];
ssize_t buf_size;
struct bts_info *info = btsdev->bts_list;
struct bts_stat *stat;
int ret, scen, index, type;
unsigned int offset, value;
char *base;
buf_size = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count);
if (buf_size < 0)
return buf_size;
buf[buf_size] = '\0';
ret = sscanf(buf, "%d %d %d %u %x\n",
&scen, &type, &index, &offset, &value);
if (ret != 5) {
pr_err("%s: sscanf failed. We need 5 inputs."\
"<SCEN DREX_TYPE IP OFFSET VALUE> count=(%d)\n",
__func__, ret);
return -EINVAL;
}
if (index >= btsdev->num_bts) {
pr_err("%s: IP index should be in range of (0 ~ %d). input=(%d)\n",
__func__, btsdev->num_bts - 1, index);
return -EINVAL;
}
if (scen >= btsdev->num_scen) {
pr_err("%s: SCEN index should be in range of (0 ~ %d). input=(%d)\n",
__func__, btsdev->num_scen - 1, scen);
return -EINVAL;
}
if (&info[index] == NULL && &info[index].stat[scen] == NULL) {
pr_err("%s: info is NULL\n", __func__);
return -ENOMEM;
}
if(type == 0) { //drex
if(offset >= sizeof(struct drex_stat))
return -EINVAL;
if(info[index].stat[scen].drex_on == 0)
return -EINVAL;
base = (char *)info[index].stat[scen].drex;
} else if (type == 1) { //drex_pf
if(offset >= sizeof(struct drex_pf_stat))
return -EINVAL;
if(info[index].stat[scen].drex_pf_on == 0)
return -EINVAL;
base = (char *)info[index].stat[scen].drex_pf;
} else {
pr_err("%s: DREX type should be in rage of (0 ~ 1). input=(%d)\n",
__func__, type);
return -EINVAL;
}
stat = info[index].stat;
spin_lock(&btsdev->lock);
*(unsigned int *)(base + offset) = value;
if (info[index].ops->set_bts != NULL) {
if (scen != btsdev->top_scen)
goto out;
if (info[index].pd_on) {
if (info[index].ops->set_bts(info[index].va_base,
&stat[scen]))
pr_warn("%s: set_drex_timeout failed. input=(%d) err=(%d)\n",
__func__, index, ret);
}
} else {
pr_err("%s: Invalid index. [%d] is %s\n", __func__, index, info[index].name);
}
out:
spin_unlock(&btsdev->lock);
return buf_size;
}
static const struct file_operations debug_bts_hwstatus_fops = {
.open = exynos_bts_hwstatus_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static const struct file_operations debug_bts_scenario_fops = {
.open = exynos_bts_scenario_open,
.read = seq_read,
.write = exynos_bts_scenario_write,
.llseek = seq_lseek,
.release = single_release,
};
static const struct file_operations debug_bts_qos_fops = {
.open = exynos_bts_qos_open,
.read = seq_read,
.write = exynos_bts_qos_write,
.llseek = seq_lseek,
.release = single_release,
};
static const struct file_operations debug_bts_mo_fops = {
.open = exynos_bts_mo_open,
.read = seq_read,
.write = exynos_bts_mo_write,
.llseek = seq_lseek,
.release = single_release,
};
static const struct file_operations debug_bts_urgent_fops = {
.open = exynos_bts_urgent_open,
.read = seq_read,
.write = exynos_bts_urgent_write,
.llseek = seq_lseek,
.release = single_release,
};
static const struct file_operations debug_bts_blocking_fops = {
.open = exynos_bts_blocking_open,
.read = seq_read,
.write = exynos_bts_blocking_write,
.llseek = seq_lseek,
.release = single_release,
};
static const struct file_operations debug_bts_log_fops = {
.open = exynos_bts_log_open,
.read = seq_read,
.write = exynos_bts_log_write,
.llseek = seq_lseek,
.release = single_release,
};
static const struct file_operations debug_bts_drex_fops = {
.open = exynos_bts_drex_open,
.read = seq_read,
.write = exynos_bts_drex_write,
.llseek = seq_lseek,
.release = single_release,
};
int exynos_bts_debugfs_init(void)
{
struct dentry *den;
den = debugfs_create_dir("bts", NULL);
if (IS_ERR(den))
return -ENOMEM;
debugfs_create_file("status", 0440, den, NULL,
&debug_bts_hwstatus_fops);
debugfs_create_file("scenario", 0440, den, NULL,
&debug_bts_scenario_fops);
debugfs_create_file("qos", 0440, den, NULL,
&debug_bts_qos_fops);
debugfs_create_file("mo", 0440, den, NULL,
&debug_bts_mo_fops);
debugfs_create_file("urgent", 0440, den, NULL,
&debug_bts_urgent_fops);
debugfs_create_file("blocking", 0440, den, NULL,
&debug_bts_blocking_fops);
debugfs_create_file("log", 0440, den, NULL,
&debug_bts_log_fops);
debugfs_create_file("drex", 0440, den, NULL,
&debug_bts_drex_fops);
return 0;
}
static int exynos_bts_syscore_suspend(void)
{
return 0;
}
static void exynos_bts_syscore_resume(void)
{
struct bts_info *info = btsdev->bts_list;
unsigned int i = 0;
spin_lock(&btsdev->lock);
for (i = 0; i < btsdev->num_bts; i++) {
if (info[i].ops->init_bts != NULL)
info[i].ops->init_bts(info[i].va_base);
bts_set(btsdev->top_scen, i);
}
spin_unlock(&btsdev->lock);
}
/* Syscore operation */
static struct syscore_ops exynos_bts_syscore_ops = {
.suspend = exynos_bts_syscore_suspend,
.resume = exynos_bts_syscore_resume,
};
/* BTS Initialize */
static int bts_initialize(struct bts_device *data)
{
struct bts_info *info = btsdev->bts_list;
unsigned int i = 0;
int ret = 0;
/* Default scenario should be enabled */
spin_lock(&btsdev->lock);
for (i = 0; i < btsdev->num_bts; i++) {
if (info[i].ops->init_bts != NULL)
info[i].ops->init_bts(info[i].va_base);
}
spin_unlock(&btsdev->lock);
btsdev->top_scen = ID_DEFAULT;
ret = bts_add_scenario(ID_DEFAULT);
if (ret) {
dev_err(data->dev, "failed to add scenario!\n");
return ret;
}
return ret;
}
static int bts_parse_setting(struct device_node *np, struct bts_stat *stat)
{
int tmp = 0, ret = 0;
of_property_read_u32(np, "stat_on", &tmp);
spin_lock(&btsdev->lock);
stat->stat_on = tmp ? true : false;
spin_unlock(&btsdev->lock);
/* Initialize */
if (stat->stat_on) {
of_property_read_u32(np, "bypass", &tmp);
stat->bypass = tmp ? true : false;
if (of_property_read_u32(np, "arqos", &(stat->arqos)))
stat->arqos = DEFAULT_QOS;
if (of_property_read_u32(np, "awqos", &(stat->awqos)))
stat->awqos = DEFAULT_QOS;
if (of_property_read_u32(np, "rmo", &(stat->rmo)))
stat->rmo = MAX_MO;
if (of_property_read_u32(np, "wmo", &(stat->wmo)))
stat->wmo = MAX_MO;
of_property_read_u32(np, "qurgent_on", &(stat->qurgent_on));
if (of_property_read_u32(np, "qurgent_th_r", &(stat->qurgent_th_r)))
stat->qurgent_th_r = MAX_QUTH;
if (of_property_read_u32(np, "qurgent_th_w", &(stat->qurgent_th_w)))
stat->qurgent_th_w = MAX_QUTH;
of_property_read_u32(np, "blocking_on", &tmp);
stat->blocking_on = tmp ? true : false;
if (of_property_read_u32(np, "qfull_limit_r", &(stat->qfull_limit_r)))
stat->qfull_limit_r = MAX_MO;
if (of_property_read_u32(np, "qfull_limit_w", &(stat->qfull_limit_w)))
stat->qfull_limit_w = MAX_MO;
if (of_property_read_u32(np, "qbusy_limit_r", &(stat->qbusy_limit_r)))
stat->qbusy_limit_r = MAX_MO;
if (of_property_read_u32(np, "qbusy_limit_w", &(stat->qbusy_limit_w)))
stat->qbusy_limit_w = MAX_MO;
if (of_property_read_u32(np, "qmax0_limit_r", &(stat->qmax0_limit_r)))
stat->qmax0_limit_r = MAX_MO;
if (of_property_read_u32(np, "qmax0_limit_w", &(stat->qmax0_limit_w)))
stat->qmax0_limit_w = MAX_MO;
if (of_property_read_u32(np, "qmax1_limit_r", &(stat->qmax1_limit_r)))
stat->qmax1_limit_r = MAX_MO;
if (of_property_read_u32(np, "qmax1_limit_w", &(stat->qmax1_limit_w)))
stat->qmax1_limit_w = MAX_MO;
/* Parsing vc-cfg */
if (of_property_read_u32(np, "vc-cfg", &(stat->vc_cfg)))
stat->vc_cfg = 0;
tmp = 0;
of_property_read_u32(np, "drex_on", &tmp);
stat->drex_on = tmp ? true: false;
if(stat->drex_on) {
stat->drex = devm_kzalloc(btsdev->dev, sizeof(struct drex_stat), GFP_KERNEL);
if(stat->drex == NULL)
goto err;
ret |= of_property_read_u32(np, "write_flush_config_0", &(stat->drex->write_flush_config_0));
ret |= of_property_read_u32(np, "write_flush_config_1", &(stat->drex->write_flush_config_1));
ret |= of_property_read_u32_array(np, "drex_timeout", stat->drex->drex_timeout, MAX_QOS + 1);
ret |= of_property_read_u32_array(np, "vc_timer_th", stat->drex->vc_timer_th, VC_TIMER_TH_NR);
ret |= of_property_read_u32(np, "cutoff_con", &(stat->drex->cutoff_con));
ret |= of_property_read_u32(np, "brb_cutoff_con", &(stat->drex->brb_cutoff_con));
ret |= of_property_read_u32(np, "wdbuf_cutoff_con", &(stat->drex->wdbuf_cutoff_con));
if(ret)
goto err;
}
tmp = 0;
of_property_read_u32(np, "drex_pf_on", &tmp);
stat->drex_pf_on = tmp ? true: false;
if(stat->drex_pf_on) {
stat->drex_pf = devm_kzalloc(btsdev->dev, sizeof(struct drex_pf_stat), GFP_KERNEL);
if(stat->drex_pf == NULL)
goto err;
ret |= of_property_read_u32(np, "pf_token_control", &(stat->drex_pf->pf_token_control));
ret |= of_property_read_u32(np, "pf_token_threshold0", &(stat->drex_pf->pf_token_threshold0));
ret |= of_property_read_u32(np, "pf_rreq_thrt_con", &(stat->drex_pf->pf_rreq_thrt_con));
ret |= of_property_read_u32(np, "pf_rreq_thrt_mo_p2", &(stat->drex_pf->pf_rreq_thrt_mo_p2));
ret |= of_property_read_u32_array(np, "pf_qos_timer", stat->drex_pf->pf_qos_timer, PF_TIMER_NR);
if(ret)
goto err;
}
}
return 0;
err:
dev_err(btsdev->dev, "%s failed", __func__);
return ret;
}
static int bts_parse_data(struct device_node *np, struct bts_device *data)
{
struct bts_scen *scen;
struct bts_info *info;
struct device_node *child_np = NULL;
struct device_node *snp = NULL;
struct resource res;
int i, j = 0;
int ret = 0;
if (of_have_populated_dt()) {
if(of_property_read_u32_array(np, "bw_params", (u32 *)&data->bw_params,
sizeof(struct bts_bw_params)/sizeof(int))) {
BTSDBG_LOG(data->dev, "Unable to parse bandwidth params\n");
ret = -EINVAL;
goto err;
}
data->num_scen = of_property_count_strings(np, "list-scen");
if (data->num_scen <= 0) {
BTSDBG_LOG(data->dev, "There should be at least one scenario\n");
ret = -EINVAL;
goto err;
}
scen = devm_kcalloc(data->dev, data->num_scen, sizeof(struct bts_scen), GFP_KERNEL);
if (scen == NULL) {
ret = -ENOMEM;
goto err;
}
for (i = 0; i < data->num_scen; i++) {
scen[i].index = i;
scen[i].status = false;
scen[i].usage_count = 0;
ret = of_property_read_string_index(np, "list-scen", i, &(scen[i].name));
if (ret < 0) {
dev_err(data->dev, "Unable to get name of bts scenarios\n");
goto err;
}
}
data->num_bts = (unsigned int)of_get_child_count(np);
if (!data->num_bts) {
BTSDBG_LOG(data->dev, "There should be at least one bts\n");
ret = -EINVAL;
goto err;
}
info = devm_kcalloc(data->dev, data->num_bts, sizeof(struct bts_info), GFP_KERNEL);
if (info == NULL) {
ret = -ENOMEM;
goto err;
}
i = 0;
for_each_child_of_node(np, child_np) {
/* Parsing scenario data */
info[i].stat = devm_kcalloc(data->dev, data->num_scen, sizeof(struct bts_stat), GFP_KERNEL);
if (info[i].stat == NULL) {
ret = -ENOMEM;
goto err;
}
/* IOREMAP physical address */
ret = of_address_to_resource(child_np, 0, &res);
if (ret) {
dev_err(data->dev, "failed to get address_to_resource\n");
if (ret == -EINVAL)
ret = 0;
} else {
info[i].va_base = devm_ioremap_resource(data->dev, &res);
if (IS_ERR(info[i].va_base)) {
dev_err(data->dev, "failed to ioremap register\n");
ret = -ENOMEM;
}
ret = of_address_to_resource(child_np, 1, &res);
if (!ret) {
info[i].stat->qos_va_base = devm_ioremap_resource(data->dev, &res);
if (IS_ERR(info[i].stat->qos_va_base)) {
dev_err(data->dev, "failed to ioremap register\n");
ret = -ENOMEM;
}
}
}
info[i].name = child_np->name;
info[i].status = of_device_is_available(child_np);
/* Parsing bts-type */
if (of_property_read_u32(child_np, "bts-type", &(info[i].type))) {
dev_warn(data->dev, "failed to get bts-type\n");
ret = -EEXIST;
goto err;
}
/* Register operation function */
ret = register_btsops(&info[i]);
if (ret)
goto err;
/* Parsing local power domain information */
if (of_property_read_u32(child_np, "cal-pdid", &(info[i].pd_id))) {
info[i].pd_id = 0;
info[i].pd_on = true;
} else {
info[i].pd_on = cal_pd_status(info[i].pd_id) ? true : false;
}
if (!of_get_child_count(child_np)) {
info[i].stat = NULL;
i++;
continue;
}
spin_lock(&btsdev->lock);
for (j = 0; j < data->num_scen; j++)
info[i].stat[j].stat_on = 0;
spin_unlock(&btsdev->lock);
for_each_child_of_node(child_np, snp) {
for (j = 0; j < data->num_scen; j++) {
if (strcmp(snp->name, scen[j].name))
continue;
bts_parse_setting(snp, &(info[i].stat[j]));
}
}
i++;
}
data->bts_bw = devm_kcalloc(data->dev, data->num_bts, sizeof(struct bts_bw), GFP_KERNEL);
if (data->bts_bw == NULL) {
ret = -ENOMEM;
goto err;
}
data->bts_list = info;
data->scen_list = scen;
exynos_cal_pd_bts_sync = bts_pd_sync;
} else {
dev_err(data->dev, "Invalid device tree node!\n");
}
err:
return ret;
}
static int bts_probe(struct platform_device *pdev)
{
int ret = 0;
btsdev = devm_kmalloc(&pdev->dev, sizeof(struct bts_device), GFP_KERNEL);
if (btsdev == NULL)
return -ENOMEM;
btsdev->dev = &pdev->dev;
if (strcmp(pdev->name, "exynos-bts") == 0) {
spin_lock_init(&btsdev->lock);
ret = bts_parse_data(btsdev->dev->of_node, btsdev);
if (ret) {
dev_err(btsdev->dev, "failed to parse data (err=%d)\n", ret);
devm_kfree(btsdev->dev, btsdev);
return ret;
}
mutex_init(&btsdev->mutex_lock);
INIT_LIST_HEAD(&btsdev->scen_node);
ret = bts_initialize(btsdev);
if (ret) {
dev_err(btsdev->dev, "failed to initialize (err=%d)\n", ret);
devm_kfree(btsdev->dev, btsdev);
return ret;
}
} else {
dev_err(btsdev->dev, "failed to get bts data from device tree\n");
}
platform_set_drvdata(pdev, btsdev);
#if defined(CONFIG_EXYNOS_PM_QOS) || defined(CONFIG_EXYNOS_PM_QOS_MODULE)
exynos_pm_qos_add_request(&exynos_mif_qos, PM_QOS_BUS_THROUGHPUT, 0);
exynos_pm_qos_add_request(&exynos_int_qos, PM_QOS_DEVICE_THROUGHPUT, 0);
#endif
#if defined(CONFIG_DEBUG_FS) || defined(CONFIG_DEBUG_FS_MODULE)
ret = exynos_bts_debugfs_init();
if (ret)
dev_err(btsdev->dev, "exynos_bts_debugfs_init failed\n");
#endif
register_syscore_ops(&exynos_bts_syscore_ops);
pr_info("%s successfully done.\n", __func__);
return ret;
}
static int bts_remove(struct platform_device *pdev)
{
devm_kfree(&pdev->dev, btsdev);
platform_set_drvdata(pdev, NULL);
return 0;
}
/* Device tree compatible information */
static const struct of_device_id exynos_bts_match[] = {
{
.compatible = "samsung,exynos-bts",
},
{},
};
MODULE_DEVICE_TABLE(of, exynos_bts_match);
static struct platform_driver bts_pdrv = {
.probe = bts_probe,
.remove = bts_remove,
.driver = {
.name = BTS_PDEV_NAME,
.of_match_table = exynos_bts_match,
},
};
static int exynos_bts_init(void)
{
int ret = 0;
ret = platform_driver_register(&bts_pdrv);
if (ret) {
pr_err("Platform driver registration failed (err=%d)\n", ret);
return ret;
}
pr_info("%s successfully done.\n", __func__);
return 0;
}
arch_initcall(exynos_bts_init);
MODULE_LICENSE("GPL");