/* * 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 #include #include #include #include #include #include #include #include #include #include #include #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. 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. 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. 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. = 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. 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. \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."\ " 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");