623 lines
15 KiB
C
Executable File
623 lines
15 KiB
C
Executable File
/* sec_nad_balancer.c
|
|
*
|
|
* NAD Balancer Driver
|
|
*
|
|
* Copyright (C) 2017 Samsung Electronics
|
|
*
|
|
* Hyeokseon Yu <hyeokseon.yu@samsung.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <linux/sec_nad_balancer.h>
|
|
#include <linux/sec_ext.h>
|
|
|
|
#define NAD_PRINT(format, ...) pr_info("[NAD_BALANCER] " format, ##__VA_ARGS__)
|
|
#define DEBUG_NAD_BALANCER
|
|
|
|
#if defined(CONFIG_SEC_FACTORY)
|
|
#ifdef CONFIG_OF
|
|
static int parse_qos_data(struct device *dev,
|
|
struct nad_balancer_platform_data *pdata,
|
|
struct device_node *np)
|
|
|
|
{
|
|
struct device_node *cnp;
|
|
struct nad_balancer_pm_qos *cqos;
|
|
int ncount = 0;
|
|
int i;
|
|
u32 freq_item;
|
|
|
|
for_each_child_of_node(np, cnp) {
|
|
cqos = &pdata->qos_items[ncount];
|
|
|
|
cqos->desc = of_get_property(cnp, "qos,label", NULL);
|
|
|
|
if (of_property_read_u32(cnp, "qos,delay_time", &cqos->delay_time)) {
|
|
dev_err(dev, "Failed to get delay time: node not exist\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (of_property_read_u32(cnp, "qos,table_size", &cqos->table_size)) {
|
|
dev_err(dev, "Failed to get table size: node not exist\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cqos->tables) {
|
|
cqos->tables = devm_kzalloc(dev, sizeof(struct freq_table) * cqos->table_size,
|
|
GFP_KERNEL);
|
|
|
|
if (!cqos->tables) {
|
|
dev_err(dev, "Failed to allocate memory of freq_table\n");
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < cqos->table_size; i++) {
|
|
if (of_property_read_u32_index(cnp, "qos,table", i, &freq_item))
|
|
return -EINVAL;
|
|
|
|
cqos->tables[i].freq = freq_item;
|
|
}
|
|
|
|
ncount++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int parse_sleep_data(struct device *dev,
|
|
struct nad_balancer_platform_data *pdata,
|
|
struct device_node *np)
|
|
|
|
{
|
|
struct nad_balancer_sleep_info *csleep;
|
|
|
|
csleep = pdata->sleep_items;
|
|
|
|
if (of_property_read_u32(np, "sleep,suspend_threshold", &csleep->suspend_threshold)) {
|
|
dev_err(dev, "Failed to get suspend time: node not exist\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (of_property_read_u32(np, "sleep,resume_threshold", &csleep->resume_threshold)) {
|
|
dev_err(dev, "Failed to get resume time: node not exist\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nad_balancer_parse_dt(struct device *dev)
|
|
{
|
|
struct nad_balancer_platform_data *pdata = dev->platform_data;
|
|
struct device_node *np;
|
|
struct device_node *qos_np;
|
|
struct device_node *sleep_np;
|
|
|
|
np = dev->of_node;
|
|
|
|
if (of_property_read_u32(np, "nad_balancer,timeout", &pdata->timeout)) {
|
|
dev_err(dev, "error to read timeout value\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pdata->nItem = of_get_child_count(np);
|
|
if (!pdata->nItem) {
|
|
dev_err(dev, "There are no items\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
qos_np = of_find_node_by_name(np, "qos");
|
|
pdata->nQos = of_get_child_count(qos_np);
|
|
pdata->qos_items = devm_kzalloc(dev,
|
|
sizeof(struct nad_balancer_pm_qos) * pdata->nQos, GFP_KERNEL);
|
|
if (!pdata->qos_items)
|
|
return -ENOMEM;
|
|
|
|
sleep_np = of_find_node_by_name(np, "sleep");
|
|
pdata->nSleep = of_get_child_count(sleep_np);
|
|
pdata->sleep_items = devm_kzalloc(dev,
|
|
sizeof(struct nad_balancer_sleep_info), GFP_KERNEL);
|
|
if (!pdata->sleep_items)
|
|
return -ENOMEM;
|
|
|
|
if (qos_np)
|
|
parse_qos_data(dev, pdata, qos_np);
|
|
|
|
if (sleep_np)
|
|
parse_sleep_data(dev, pdata, sleep_np);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
void report_sleep_info(struct device *dev, pm_message_t state,
|
|
unsigned long long usec)
|
|
{
|
|
struct nad_balancer_info *pinfo;
|
|
struct nad_balancer_sleep_info *psleep;
|
|
|
|
if (!sleep_test_enable)
|
|
return;
|
|
|
|
pinfo = dev_get_drvdata(sec_nad_balancer);
|
|
psleep = pinfo->pdata->sleep_items;
|
|
|
|
switch (state.event) {
|
|
case PM_EVENT_SUSPEND:
|
|
if ((usec / USEC_PER_MSEC) > psleep->suspend_threshold) {
|
|
panic("NAD_BALANCER: over suspend time! dev:%s, %lld.%03lld msecs\n"
|
|
,dev_name(dev), usec / USEC_PER_MSEC, usec % USEC_PER_MSEC);
|
|
}
|
|
break;
|
|
case PM_EVENT_RESUME:
|
|
if ((usec / USEC_PER_MSEC) > psleep->resume_threshold) {
|
|
panic("NAD_BALANCER: over resume time! dev:%s, %lld.%03lld msecs\n"
|
|
, dev_name(dev), usec / USEC_PER_MSEC, usec % USEC_PER_MSEC);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool sec_nad_balancer_enabled(void)
|
|
{
|
|
return nad_balancer_enable;
|
|
}
|
|
|
|
static void sec_nad_balancer_enable(void)
|
|
{
|
|
mutex_lock(&nad_balancer_lock);
|
|
nad_balancer_enable = true;
|
|
mutex_unlock(&nad_balancer_lock);
|
|
}
|
|
|
|
static void sec_nad_balancer_disable(void)
|
|
{
|
|
mutex_lock(&nad_balancer_lock);
|
|
nad_balancer_enable = false;
|
|
mutex_unlock(&nad_balancer_lock);
|
|
}
|
|
|
|
static void sec_nad_balancer_timeout_work(struct work_struct *work)
|
|
{
|
|
NAD_PRINT("exprired nad qos work.\n");
|
|
sec_nad_balancer_disable();
|
|
}
|
|
|
|
static void update_temperature(struct nad_balancer_pm_qos *pqos, int type)
|
|
{
|
|
#if !defined(CONFIG_SEC_BOOTSTAT)
|
|
return;
|
|
#endif
|
|
int temp[MAX_TMU_COUNT];
|
|
int i;
|
|
|
|
if (type == UPDATE_FIRST_TEMPERATURE) {
|
|
sec_bootstat_get_thermal(pqos->temperature);
|
|
NAD_PRINT("first temperature ");
|
|
for (i = 0; i < MAX_TMU_COUNT; i++)
|
|
pr_info("[%d]", pqos->temperature[i]);
|
|
pr_info("\n");
|
|
} else if (type == UPDATE_CONTI_TEMPERATURE) {
|
|
sec_bootstat_get_thermal(temp);
|
|
for (i = 0; i < MAX_TMU_COUNT; i++) {
|
|
if (pqos->temperature[i] != max(pqos->temperature[i], temp[i])) {
|
|
NAD_PRINT("update temperature[%d] prev[%d] new [%d]\n",
|
|
i, pqos->temperature[i], temp[i]);
|
|
pqos->temperature[i] = temp[i];
|
|
}
|
|
}
|
|
} else if (type == UPDATE_FINAL_TEMPERATURE) {
|
|
sec_bootstat_get_thermal(temp);
|
|
for (i = 0; i < MAX_TMU_COUNT; i++) {
|
|
if (pqos->temperature[i] != max(pqos->temperature[i], temp[i])) {
|
|
NAD_PRINT("update temperature[%d] prev[%d] new [%d]\n",
|
|
i, pqos->temperature[i], temp[i]);
|
|
pqos->temperature[i] = temp[i];
|
|
}
|
|
}
|
|
/* print out final temperature */
|
|
NAD_PRINT("final temperature [MNGS][APOLLO][GPU][ISP] = ");
|
|
for (i = 0; i < MAX_TMU_COUNT; i++)
|
|
pr_info("[%d]", pqos->temperature[i]);
|
|
pr_info("\n");
|
|
}
|
|
}
|
|
|
|
static int on_run(void *data)
|
|
{
|
|
struct nad_balancer_pm_qos *pqos = data;
|
|
int delay = pqos->delay_time;
|
|
int policy = pqos->policy;
|
|
int idx = 0;
|
|
int req_lit = 0;
|
|
int req_big = 0;
|
|
int req_mif = 0;
|
|
int temp_check_period = 0;
|
|
|
|
NAD_PRINT("%s start.\n", pqos->desc);
|
|
while (!kthread_should_stop()) {
|
|
if (!sec_nad_balancer_enabled()) {
|
|
NAD_PRINT("%s no more working nad balancer qos thread.\n", pqos->desc);
|
|
if (!strncmp(pqos->desc, "LIT", 3) && req_lit == 1)
|
|
REMOVE_PM_QOS(&pqos->lit_qos);
|
|
if (!strncmp(pqos->desc, "BIG", 3) && req_big == 1)
|
|
REMOVE_PM_QOS(&pqos->big_qos);
|
|
if (!strncmp(pqos->desc, "MIF", 3) && req_mif == 1)
|
|
REMOVE_PM_QOS(&pqos->mif_qos);
|
|
|
|
/* update final temperature */
|
|
if (!strncmp(pqos->desc, "LIT", 3))
|
|
update_temperature(pqos, UPDATE_FINAL_TEMPERATURE);
|
|
|
|
break;
|
|
}
|
|
|
|
idx = prandom_u32() % pqos->table_size;
|
|
|
|
/* limit qos cl0 min throughput */
|
|
if (!strncmp(pqos->desc, "LIT", 3)) {
|
|
if (req_lit == 0) {
|
|
UPDATE_PM_QOS(&pqos->lit_qos, policy ?
|
|
PM_QOS_CLUSTER0_FREQ_MAX : PM_QOS_CLUSTER0_FREQ_MIN,
|
|
pqos->tables[idx].freq);
|
|
req_lit = 1;
|
|
} else {
|
|
REMOVE_PM_QOS(&pqos->lit_qos);
|
|
req_lit = 0;
|
|
}
|
|
temp_check_period++;
|
|
if (temp_check_period % 50 == 0)
|
|
update_temperature(pqos, UPDATE_CONTI_TEMPERATURE);
|
|
}
|
|
|
|
/* limit qos cl1 max throughput */
|
|
if (!strncmp(pqos->desc, "BIG", 3)) {
|
|
if (req_big == 0) {
|
|
UPDATE_PM_QOS(&pqos->big_qos, policy ?
|
|
PM_QOS_CLUSTER1_FREQ_MAX : PM_QOS_CLUSTER1_FREQ_MIN,
|
|
pqos->tables[idx].freq);
|
|
req_big = 1;
|
|
} else {
|
|
REMOVE_PM_QOS(&pqos->big_qos);
|
|
req_big = 0;
|
|
}
|
|
}
|
|
|
|
/* limit bus max throughput */
|
|
if (!strncmp(pqos->desc, "MIF", 3)) {
|
|
if (req_mif == 0) {
|
|
UPDATE_PM_QOS(&pqos->mif_qos, policy ?
|
|
PM_QOS_BUS_THROUGHPUT_MAX : PM_QOS_BUS_THROUGHPUT,
|
|
pqos->tables[idx].freq);
|
|
req_mif = 1;
|
|
} else {
|
|
REMOVE_PM_QOS(&pqos->mif_qos);
|
|
req_mif = 0;
|
|
}
|
|
}
|
|
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
schedule_timeout_interruptible(msecs_to_jiffies(delay));
|
|
set_current_state(TASK_RUNNING);
|
|
}
|
|
NAD_PRINT("%s thread done.\n", pqos->desc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_policy_num(char *str)
|
|
{
|
|
if (!strncmp(str, "max", 3))
|
|
return NAD_MAX_FREQ;
|
|
else
|
|
return NAD_MIN_FREQ;
|
|
}
|
|
|
|
static ssize_t store_nad_balancer(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct nad_balancer_info *info = dev_get_drvdata(dev);
|
|
struct nad_balancer_pm_qos *pqos;
|
|
char cmd_temp[NAD_BUFF_SIZE];
|
|
char nad_cmd[BALANCER_CMD][NAD_BUFF_SIZE];
|
|
char *nad_ptr, *string;
|
|
int i, j, idx = 0, ret = -1, expire_time;
|
|
unsigned int len = 0;
|
|
|
|
/* Copy buf to nad cmd */
|
|
len = (unsigned int)min(count, sizeof(cmd_temp) - 1);
|
|
strncpy(cmd_temp, buf, len);
|
|
cmd_temp[len] = '\0';
|
|
string = cmd_temp;
|
|
|
|
/* Parse AT CMD */
|
|
while (idx < BALANCER_CMD) {
|
|
nad_ptr = strsep(&string, ",");
|
|
if (nad_ptr == NULL || strlen(nad_ptr) >= NAD_BUFF_SIZE) {
|
|
NAD_PRINT(" %s: invalid input\n",__func__);
|
|
return -EINVAL;
|
|
}
|
|
strcpy(nad_cmd[idx++], nad_ptr);
|
|
}
|
|
|
|
/* Get thread expire time */
|
|
ret = sscanf(nad_cmd[1], "%d\n", &expire_time);
|
|
if (ret != 1)
|
|
return -EINVAL;
|
|
|
|
#if defined(DEBUG_NAD_BALANCER)
|
|
NAD_PRINT("cmd : %s, expire_time : %d\n", nad_cmd[0], expire_time);
|
|
|
|
idx = 0;
|
|
while (idx < BALANCER_CMD) {
|
|
NAD_PRINT("cmd[%d] : %s\n", idx, nad_cmd[idx]);
|
|
idx++;
|
|
}
|
|
|
|
for (i = 0; i < info->pdata->nQos; i++) {
|
|
pqos = &info->pdata->qos_items[i];
|
|
NAD_PRINT("%s freq table\n", pqos->desc);
|
|
|
|
for(j = 0; j < pqos->table_size; j++) {
|
|
NAD_PRINT("%d\n", pqos->tables[j].freq);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
sleep_test_enable = true;
|
|
|
|
if (!strncmp(buf, "start", 5)) {
|
|
/* Enable qos thread */
|
|
if (sec_nad_balancer_enabled()) {
|
|
NAD_PRINT("already running skip start nad balancer!\n");
|
|
return count;
|
|
}
|
|
sec_nad_balancer_enable();
|
|
for (i = 0; i < info->pdata->nQos; i++) {
|
|
pqos = &info->pdata->qos_items[i];
|
|
if (i == 0)
|
|
update_temperature(pqos, UPDATE_FIRST_TEMPERATURE);
|
|
/* set qos policy */
|
|
info->pdata->qos_items[i].policy =
|
|
get_policy_num(nad_cmd[i + 2]);
|
|
#if defined(DEBUG_NAD_BALANCER)
|
|
NAD_PRINT("%s thread use %s qos policy\n",
|
|
info->pdata->qos_items[i].desc, nad_cmd[i + 2]);
|
|
#endif
|
|
|
|
info->pdata->qos_items[i].thread = kthread_run(on_run, pqos, "nad_balancer_qos_thread");
|
|
if (IS_ERR_OR_NULL(info->pdata->qos_items[i].thread)) {
|
|
NAD_PRINT("Failed in creation of thread.\n");
|
|
return count;
|
|
}
|
|
}
|
|
|
|
/* Trigger delayed workqueue */
|
|
schedule_delayed_work(&info->sec_nad_balancer_work, HZ * expire_time);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static ssize_t show_nad_balancer(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
NAD_PRINT("%s\n", __func__);
|
|
sec_nad_balancer_disable();
|
|
sleep_test_enable = false;
|
|
|
|
return sprintf(buf, "OK\n");
|
|
}
|
|
static DEVICE_ATTR(balancer, 0644, show_nad_balancer, store_nad_balancer);
|
|
|
|
static ssize_t store_nad_status(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
char *envp[2] = {"NAD_BALANCER_UEVENT", NULL};
|
|
|
|
NAD_PRINT("%s\n", __func__);
|
|
|
|
kobject_uevent_env(&sec_nad_balancer->kobj, KOBJ_CHANGE, envp);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t show_nad_status(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
NAD_PRINT("%s\n", __func__);
|
|
|
|
return sprintf(buf, "OK\n");
|
|
}
|
|
|
|
static DEVICE_ATTR(status, 0644, show_nad_status, store_nad_status);
|
|
|
|
static ssize_t store_nad_timeout(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
NAD_PRINT("%s\n", __func__);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t show_nad_timeout(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct nad_balancer_info *pinfo = dev_get_drvdata(dev);
|
|
int timeout_val = pinfo->pdata->timeout;
|
|
|
|
NAD_PRINT("%s: timeout val (%d)\n", __func__, timeout_val);
|
|
|
|
return sprintf(buf, "%d\n", timeout_val);
|
|
}
|
|
|
|
static DEVICE_ATTR(timeout, 0644, show_nad_timeout, store_nad_timeout);
|
|
|
|
static const struct dev_pm_ops sec_nad_balancer_pm = {
|
|
.prepare = sec_nad_balancer_prepare,
|
|
.resume = sec_nad_balancer_resume,
|
|
};
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id sec_nad_balancer_dt_match[] = {
|
|
{ .compatible = "samsung,sec_nad_balancer" },
|
|
{ }
|
|
};
|
|
#endif
|
|
|
|
static struct platform_driver sec_nad_balancer_driver = {
|
|
.probe = sec_nad_balancer_probe,
|
|
.remove = sec_nad_balancer_remove,
|
|
.driver = {
|
|
.name = "sec_nad_balancer",
|
|
.owner = THIS_MODULE,
|
|
#if defined(CONFIG_PM)
|
|
.pm = &sec_nad_balancer_pm,
|
|
#endif
|
|
#if CONFIG_OF
|
|
.of_match_table = of_match_ptr(sec_nad_balancer_dt_match),
|
|
#endif
|
|
},
|
|
};
|
|
|
|
static int sec_nad_balancer_prepare(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int sec_nad_balancer_resume(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int sec_nad_balancer_remove(struct platform_device *pdev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int sec_nad_balancer_probe(struct platform_device *pdev)
|
|
{
|
|
struct nad_balancer_platform_data *pdata;
|
|
struct nad_balancer_info *pinfo;
|
|
int ret = 0;
|
|
|
|
NAD_PRINT("%s\n", __func__);
|
|
if (pdev->dev.of_node) {
|
|
pdata = devm_kzalloc(&pdev->dev,
|
|
sizeof(struct nad_balancer_platform_data), GFP_KERNEL);
|
|
|
|
if (!pdata) {
|
|
dev_err(&pdev->dev, "Failed to allocate platform data\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
pdev->dev.platform_data = pdata;
|
|
ret = nad_balancer_parse_dt(&pdev->dev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to parse dt data\n");
|
|
return ret;
|
|
}
|
|
|
|
pr_info("%s: parse dt done\n", __func__);
|
|
} else {
|
|
pdata = pdev->dev.platform_data;
|
|
}
|
|
|
|
if (!pdata) {
|
|
dev_err(&pdev->dev, "There are no platform data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!pdata->nItem) {
|
|
dev_err(&pdev->dev, "There are no devices\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pinfo = devm_kzalloc(&pdev->dev, sizeof(struct nad_balancer_info), GFP_KERNEL);
|
|
|
|
if (!pinfo)
|
|
return -ENOMEM;
|
|
|
|
pinfo->dev = sec_device_create(pinfo, "sec_nad_balancer");
|
|
if (IS_ERR(sec_nad_balancer)) {
|
|
pr_err("%s Failed to create device(sec_nad_balancer)!\n", __func__);
|
|
ret = -ENODEV;
|
|
goto out;
|
|
}
|
|
ret = device_create_file(pinfo->dev, &dev_attr_balancer);
|
|
if (ret) {
|
|
pr_err("%s: Failed to create device file\n", __func__);
|
|
goto err_create_nad_balancer_sysfs;
|
|
}
|
|
ret = device_create_file(pinfo->dev, &dev_attr_status);
|
|
if (ret) {
|
|
pr_err("%s: Failed to create device file\n", __func__);
|
|
goto err_create_nad_balancer_sysfs;
|
|
}
|
|
ret = device_create_file(pinfo->dev, &dev_attr_timeout);
|
|
if (ret) {
|
|
pr_err("%s: Failed to create device file\n", __func__);
|
|
goto err_create_nad_balancer_sysfs;
|
|
}
|
|
|
|
INIT_DELAYED_WORK(&pinfo->sec_nad_balancer_work,
|
|
sec_nad_balancer_timeout_work);
|
|
|
|
pinfo->pdata = pdata;
|
|
sec_nad_balancer = pinfo->dev;
|
|
platform_set_drvdata(pdev, pinfo);
|
|
|
|
return ret;
|
|
|
|
err_create_nad_balancer_sysfs:
|
|
sec_device_destroy(sec_nad_balancer->devt);
|
|
out:
|
|
if (!pinfo)
|
|
devm_kfree(&pdev->dev, pinfo);
|
|
if (!pdata)
|
|
devm_kfree(&pdev->dev, pdata);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static int __init sec_nad_balancer_init(void)
|
|
{
|
|
#if defined(CONFIG_SEC_FACTORY)
|
|
NAD_PRINT("%s\n", __func__);
|
|
return platform_driver_register(&sec_nad_balancer_driver);
|
|
#else
|
|
NAD_PRINT("Not support NAD balancer.\n");
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static void __exit sec_nad_balancer_exit(void)
|
|
{
|
|
#if defined(CONFIG_SEC_FACTORY)
|
|
NAD_PRINT("%s\n", __func__);
|
|
return platform_driver_unregister(&sec_nad_balancer_driver);
|
|
#endif
|
|
}
|
|
|
|
module_init(sec_nad_balancer_init);
|
|
module_exit(sec_nad_balancer_exit);
|
|
|
|
MODULE_DESCRIPTION("Samsung NAD balancer Driver");
|
|
MODULE_AUTHOR("Samsung Electronics");
|
|
MODULE_LICENSE("GPL");
|