/* * Copyright (c) 2015 Samsung Electronics Co., Ltd. * http://www.samsung.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "cal_bts7870.h" #include "regs-bts.h" #define MAX_MIF_IDX 7 #define FHD_BW (1920 * 1080 * 4 * 60) #ifdef BTS_DBGGEN #define BTS_DBG(x...) pr_err(x) #else #define BTS_DBG(x...) do {} while (0) #endif #define BTS_SYSREG_DOMAIN (BTS_DISPAUD | \ BTS_ISP0 | \ BTS_ISP1 | \ BTS_MFCMSCL | \ BTS_JPEG | \ BTS_MSCL0 | \ BTS_MSCL1 | \ BTS_AUD | \ BTS_FSYS0 | \ BTS_FSYS1 | \ BTS_FSYS2 | \ BTS_MODAPIF_CP | \ BTS_MODAPIF_GNSS | \ BTS_CPU | \ BTS_APL) enum bts_index { BTS_IDX_DISPAUD, BTS_IDX_ISP0, BTS_IDX_ISP1, BTS_IDX_MFCMSCL, BTS_IDX_JPEG, BTS_IDX_MSCL0, BTS_IDX_MSCL1, BTS_IDX_AUD, BTS_IDX_FSYS0, BTS_IDX_FSYS1, BTS_IDX_FSYS2, BTS_IDX_MODAPIF_CP, BTS_IDX_MODAPIF_GNSS, BTS_IDX_CPU, BTS_IDX_APL, BTS_IDX_G3D, BTS_MAX, }; enum bts_id { BTS_DISPAUD = (1 << BTS_IDX_DISPAUD), BTS_ISP0 = (1 << BTS_IDX_ISP0), BTS_ISP1 = (1 << BTS_IDX_ISP1), BTS_MFCMSCL = (1 << BTS_IDX_MFCMSCL), BTS_JPEG = (1 << BTS_IDX_JPEG), BTS_MSCL0 = (1 << BTS_IDX_MSCL0), BTS_MSCL1 = (1 << BTS_IDX_MSCL1), BTS_AUD = (1 << BTS_IDX_AUD), BTS_FSYS0 = (1 << BTS_IDX_FSYS0), BTS_FSYS1 = (1 << BTS_IDX_FSYS1), BTS_FSYS2 = (1 << BTS_IDX_FSYS2), BTS_MODAPIF_CP = (1 << BTS_IDX_MODAPIF_CP), BTS_MODAPIF_GNSS = (1 << BTS_IDX_MODAPIF_GNSS), BTS_CPU = (1 << BTS_IDX_CPU), BTS_APL = (1 << BTS_IDX_APL), BTS_G3D = (1 << BTS_IDX_G3D), }; enum exynos_bts_scenario { BS_DISABLE, BS_DEFAULT, BS_CAM_BNS, BS_DEBUG, BS_MAX, }; enum exynos_bts_function { BF_SETQOS, BF_SETQOS_BW, BF_SETQOS_MO, BF_DISABLE, BF_NOP, }; struct bts_table { enum exynos_bts_function fn; unsigned int priority[2]; unsigned int window; unsigned int token; unsigned int mo; struct bts_info *next_bts; int prev_scen; int next_scen; }; struct bts_info { enum bts_id id; const char *name; unsigned int pa_base; void __iomem *va_base; struct bts_table table[BS_MAX]; const char *pd_name; bool on; struct list_head list; bool enable; struct clk_info *ct_ptr; enum exynos_bts_scenario cur_scen; enum exynos_bts_scenario top_scen; }; struct bts_scenario { const char *name; unsigned int ip; enum exynos_bts_scenario id; struct bts_info *head; }; struct clk_info { const char *clk_name; struct clk *clk; enum bts_index index; }; static struct pm_qos_request exynos7_mif_bts_qos; static struct pm_qos_request exynos7_int_bts_qos; static struct srcu_notifier_head exynos_media_notifier; static struct clk_info clk_table[] = { {"gate_g3d_bts_alias", NULL, BTS_IDX_G3D}, }; static DEFINE_MUTEX(media_mutex); static unsigned int decon_bw, cam_bw, total_bw; static void __iomem *base_drex; static void __iomem *base_bts_pmu_alive; static unsigned int mif_freq; static unsigned int num_active_disp_win; static struct bts_info exynos7_bts[] = { [BTS_IDX_DISPAUD] = { .id = BTS_DISPAUD, .name = "disp", .pa_base = EXYNOS7870_PA_SYSREG_DISPAUD, .pd_name = "pd-dispaud", .table[BS_DEFAULT].fn = BF_SETQOS, .table[BS_DEFAULT].priority[0] = 0xA, .table[BS_DISABLE].fn = BF_SETQOS, .table[BS_DISABLE].priority[0] = 0x4, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_ISP0] = { .id = BTS_ISP0, .name = "isp0", .pa_base = EXYNOS7870_PA_SYSREG_ISP, .pd_name = "pd-cam0", .table[BS_DEFAULT].fn = BF_SETQOS, .table[BS_DEFAULT].priority[0] = 0xC, .table[BS_DEFAULT].priority[1] = 0xC, .table[BS_CAM_BNS].fn = BF_SETQOS, .table[BS_CAM_BNS].priority[0] = 0xC, .table[BS_CAM_BNS].priority[1] = 0xC, .table[BS_DISABLE].fn = BF_SETQOS, .table[BS_DISABLE].priority[0] = 0x4, .table[BS_DISABLE].priority[1] = 0x4, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_ISP1] = { .id = BTS_ISP1, .name = "isp1", .pa_base = EXYNOS7870_PA_SYSREG_ISP, .pd_name = "pd-cam0", .table[BS_DEFAULT].fn = BF_SETQOS, .table[BS_DEFAULT].priority[0] = 0xC, .table[BS_DEFAULT].priority[1] = 0xC, .table[BS_CAM_BNS].fn = BF_SETQOS, .table[BS_CAM_BNS].priority[0] = 0xC, .table[BS_CAM_BNS].priority[1] = 0xC, .table[BS_DISABLE].fn = BF_SETQOS, .table[BS_DISABLE].priority[0] = 0x4, .table[BS_DISABLE].priority[1] = 0x4, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_MFCMSCL] = { .id = BTS_MFCMSCL, .name = "mfc", .pa_base = EXYNOS7870_PA_SYSREG_MFCMSCL, .pd_name = "pd-mfcmscl", .table[BS_DEFAULT].fn = BF_SETQOS, .table[BS_DEFAULT].priority[0] = 0x8, .table[BS_DISABLE].fn = BF_SETQOS, .table[BS_DISABLE].priority[0] = 0x4, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_FSYS0] = { .id = BTS_FSYS0, .name = "fsys0", .pa_base = EXYNOS7870_PA_SYSREG_FSYS, .pd_name = "pd-fsys", .table[BS_DEFAULT].fn = BF_NOP, .table[BS_DISABLE].fn = BF_SETQOS, .table[BS_DISABLE].priority[0] = 0x4, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_FSYS1] = { .id = BTS_FSYS1, .name = "fsys1", .pa_base = EXYNOS7870_PA_SYSREG_FSYS, .pd_name = "pd-fsys", .table[BS_DEFAULT].fn = BF_NOP, .table[BS_DISABLE].fn = BF_SETQOS, .table[BS_DISABLE].priority[0] = 0x4, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_FSYS2] = { .id = BTS_FSYS2, .name = "fsys2", .pa_base = EXYNOS7870_PA_SYSREG_FSYS, .pd_name = "pd-fsys", .table[BS_DEFAULT].fn = BF_NOP, .table[BS_DISABLE].fn = BF_SETQOS, .table[BS_DISABLE].priority[0] = 0x4, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_MODAPIF_CP] = { .id = BTS_MODAPIF_CP, .name = "modapif_cp", .pa_base = EXYNOS7870_PA_PMU_ALIVE, .pd_name = "pd-modapif", .table[BS_DEFAULT].fn = BF_SETQOS, .table[BS_DEFAULT].priority[0] = 0xD, .table[BS_DISABLE].fn = BF_SETQOS, .table[BS_DISABLE].priority[0] = 0x4, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_MODAPIF_GNSS] = { .id = BTS_MODAPIF_GNSS, .name = "modapif_gnss", .pa_base = EXYNOS7870_PA_PMU_ALIVE, .pd_name = "pd-modapif", .table[BS_DEFAULT].fn = BF_SETQOS, .table[BS_DEFAULT].priority[0] = 0x4, .table[BS_DISABLE].fn = BF_SETQOS, .table[BS_DISABLE].priority[0] = 0x4, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_CPU] = { .id = BTS_CPU, .name = "cpu", .pa_base = EXYNOS7870_PA_SYSREG_MIF, .pd_name = "pd-cpu", .table[BS_DEFAULT].fn = BF_SETQOS, .table[BS_DEFAULT].priority[0] = 0x4, .table[BS_DISABLE].fn = BF_SETQOS, .table[BS_DISABLE].priority[0] = 0x4, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_APL] = { .id = BTS_APL, .name = "apl", .pa_base = EXYNOS7870_PA_SYSREG_MIF, .pd_name = "pd-apl", .table[BS_DEFAULT].fn = BF_SETQOS, .table[BS_DEFAULT].priority[0] = 0x4, .table[BS_DISABLE].fn = BF_SETQOS, .table[BS_DISABLE].priority[0] = 0x4, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_G3D] = { .id = BTS_G3D, .name = "g3d", .pa_base = EXYNOS7870_PA_BTS_G3D, .pd_name = "pd-g3d", .table[BS_DEFAULT].fn = BF_SETQOS_MO, .table[BS_DEFAULT].priority[0] = 0x44444444, .table[BS_DEFAULT].mo = 0x3f, .table[BS_DISABLE].fn = BF_DISABLE, .cur_scen = BS_DISABLE, .on = false, .enable = false, }, }; static struct bts_scenario bts_scen[] = { [BS_DISABLE] = { .name = "bts_disable", .id = BS_DISABLE, }, [BS_DEFAULT] = { .name = "bts_default", .id = BS_DEFAULT, }, [BS_CAM_BNS] = { .name = "bts_cam_bns", .id = BS_CAM_BNS, }, [BS_DEBUG] = { .name = "bts_debugging_ip", .id = BS_DEBUG, }, [BS_MAX] = { .name = "undefined" } }; static DEFINE_SPINLOCK(bts_lock); static LIST_HEAD(bts_list); static void bts_clk_on(struct bts_info *bts) { struct clk_info *ptr; enum bts_index btstable_index; ptr = bts->ct_ptr; if (ptr) { btstable_index = ptr->index; do { clk_enable(ptr->clk); } while (++ptr < clk_table + ARRAY_SIZE(clk_table) && ptr->index == btstable_index); } } static void bts_clk_off(struct bts_info *bts) { struct clk_info *ptr; enum bts_index btstable_index; ptr = bts->ct_ptr; if (ptr) { btstable_index = ptr->index; do { clk_disable(ptr->clk); } while (++ptr < clk_table + ARRAY_SIZE(clk_table) && ptr->index == btstable_index); } } static void bts_setqos_ip_sysreg(enum bts_id id, void __iomem *va_base, unsigned int *priority) { switch (id) { case BTS_DISPAUD: bts_setqos_sysreg(BTS_SYSREG_DISPAUD, va_base, priority); break; case BTS_ISP0: bts_setqos_sysreg(BTS_SYSREG_ISP0, va_base, priority); break; case BTS_ISP1: bts_setqos_sysreg(BTS_SYSREG_ISP1, va_base, priority); break; case BTS_MFCMSCL: bts_setqos_sysreg(BTS_SYSREG_MFCMSCL, va_base, priority); break; case BTS_FSYS0: bts_setqos_sysreg(BTS_SYSREG_FSYS0, va_base, priority); break; case BTS_FSYS1: bts_setqos_sysreg(BTS_SYSREG_FSYS1, va_base, priority); break; case BTS_FSYS2: bts_setqos_sysreg(BTS_SYSREG_FSYS2, va_base, priority); break; case BTS_MODAPIF_CP: bts_setqos_sysreg(BTS_SYSREG_MIF_MODAPIF_CP, va_base, priority); break; case BTS_MODAPIF_GNSS: bts_setqos_sysreg(BTS_SYSREG_MIF_MODAPIF_GNSS, va_base, priority); break; case BTS_CPU: bts_setqos_sysreg(BTS_SYSREG_MIF_CPU, va_base, priority); break; case BTS_APL: bts_setqos_sysreg(BTS_SYSREG_MIF_APL, va_base, priority); break; default: break; } } static void bts_setmo_ip_sysreg(enum bts_id id, void __iomem *va_base, unsigned int ar, unsigned int aw) { return; } static void bts_set_ip_table(enum exynos_bts_scenario scen, struct bts_info *bts) { enum exynos_bts_function fn = bts->table[scen].fn; bool on; BTS_DBG("[BTS] %s on:%d bts scen: [%s]->[%s]\n", bts->name, bts->on, bts_scen[bts->cur_scen].name, bts_scen[scen].name); switch (fn) { case BF_SETQOS: bts_setqos_ip_sysreg(bts->id, bts->va_base, \ bts->table[scen].priority); if (bts->id == BTS_ISP0) { on = (scen == BS_DEFAULT || scen == BS_CAM_BNS) ? true : false; bts_setotf_sysreg(BTS_SYSREG_ISP_VRA_SEL, \ bts->va_base, on); bts_setotf_sysreg(BTS_SYSREG_ISP_ISP_SCL_SEL, \ bts->va_base, on); } break; case BF_SETQOS_BW: break; case BF_SETQOS_MO: if (bts->id & BTS_SYSREG_DOMAIN) bts_setmo_ip_sysreg(bts->id, bts->va_base, \ bts->table[scen].mo, \ bts->table[scen].mo); else if (bts->id == BTS_G3D) bts_setqos_mo(bts->va_base, bts->table[scen].priority[0], \ bts->table[scen].mo); break; case BF_DISABLE: break; case BF_NOP: break; } bts->cur_scen = scen; } static enum exynos_bts_scenario bts_get_scen(struct bts_info *bts) { enum exynos_bts_scenario scen; scen = BS_DEFAULT; return scen; } static void bts_add_scen(enum exynos_bts_scenario scen, struct bts_info *bts) { struct bts_info *first = bts; int next = 0; int prev = 0; if (!bts) return; BTS_DBG("[bts %s] scen %s off\n", bts->name, bts_scen[scen].name); do { if (bts->enable) { if (bts->table[scen].next_scen == 0) { if (scen >= bts->top_scen) { bts->table[scen].prev_scen = bts->top_scen; bts->table[bts->top_scen].next_scen = scen; bts->top_scen = scen; bts->table[scen].next_scen = -1; if (bts->on) bts_set_ip_table(bts->top_scen, bts); } else { for (prev = bts->top_scen; prev > scen; prev = bts->table[prev].prev_scen) next = prev; bts->table[scen].prev_scen = bts->table[next].prev_scen; bts->table[scen].next_scen = bts->table[prev].next_scen; bts->table[next].prev_scen = scen; bts->table[prev].next_scen = scen; } } } bts = bts->table[scen].next_bts; } while (bts && bts != first); } static void bts_del_scen(enum exynos_bts_scenario scen, struct bts_info *bts) { struct bts_info *first = bts; int next = 0; int prev = 0; if (!bts) return; BTS_DBG("[bts %s] scen %s off\n", bts->name, bts_scen[scen].name); do { if (bts->enable) { if (bts->table[scen].next_scen != 0) { if (scen == bts->top_scen) { prev = bts->table[scen].prev_scen; bts->top_scen = prev; bts->table[prev].next_scen = -1; bts->table[scen].next_scen = 0; bts->table[scen].prev_scen = 0; if (bts->on) bts_set_ip_table(prev, bts); } else if (scen < bts->top_scen) { prev = bts->table[scen].prev_scen; next = bts->table[scen].next_scen; bts->table[next].prev_scen = bts->table[scen].prev_scen; bts->table[prev].next_scen = bts->table[scen].next_scen; bts->table[scen].prev_scen = 0; bts->table[scen].next_scen = 0; } else { BTS_DBG("%s scenario couldn't exist above top_scen\n", bts_scen[scen].name); } } } bts = bts->table[scen].next_bts; } while (bts && bts != first); } void bts_scen_update(enum bts_scen_type type, unsigned int val) { enum exynos_bts_scenario scen = BS_DEFAULT; struct bts_info *bts = NULL; bool on; spin_lock(&bts_lock); switch (type) { case TYPE_CAM_BNS: on = val ? true : false; scen = BS_CAM_BNS; bts = &exynos7_bts[BTS_IDX_ISP0]; BTS_DBG("[BTS] CAM_BNS: %s\n", bts_scen[scen].name); break; default: spin_unlock(&bts_lock); return; } if (on) bts_add_scen(scen, bts); else bts_del_scen(scen, bts); spin_unlock(&bts_lock); } void bts_initialize(const char *pd_name, bool on) { struct bts_info *bts; enum exynos_bts_scenario scen = BS_DISABLE; spin_lock(&bts_lock); list_for_each_entry(bts, &bts_list, list) if (pd_name && bts->pd_name && !strncmp(bts->pd_name, pd_name, strlen(pd_name))) { BTS_DBG("[BTS] %s on/off:%d->%d\n", bts->name, bts->on, on); if (!bts->on && on) bts_clk_on(bts); else if (bts->on && !on) bts_clk_off(bts); if (!bts->enable) { bts->on = on; continue; } scen = bts_get_scen(bts); if (on) { bts_add_scen(scen, bts); if (!bts->on) { bts->on = true; bts_set_ip_table(bts->top_scen, bts); } } else { if (bts->on) bts->on = false; bts_del_scen(scen, bts); } } spin_unlock(&bts_lock); } static void scen_chaining(enum exynos_bts_scenario scen) { struct bts_info *prev = NULL; struct bts_info *first = NULL; struct bts_info *bts; if (bts_scen[scen].ip) { list_for_each_entry(bts, &bts_list, list) { if (bts_scen[scen].ip & bts->id) { if (!first) first = bts; if (prev) prev->table[scen].next_bts = bts; prev = bts; } } if (prev) prev->table[scen].next_bts = first; bts_scen[scen].head = first; } } static int exynos7_qos_status_open_show(struct seq_file *buf, void *d) { unsigned int i; unsigned int val_r; spin_lock(&bts_lock); for (i = 0; i < ARRAY_SIZE(exynos7_bts); i++) { seq_printf(buf, "bts[%d] %s : ", i, exynos7_bts[i].name); if (exynos7_bts[i].on) { if (exynos7_bts[i].ct_ptr) bts_clk_on(exynos7_bts + i); /* axi qoscontrol */ switch (exynos7_bts[i].id) { case BTS_DISPAUD: val_r = __raw_readl(exynos7_bts[i].va_base + DISPAUD_QOS_CON); break; case BTS_ISP0: val_r = __raw_readl(exynos7_bts[i].va_base + ISP_QOS_CON0); break; case BTS_ISP1: val_r = __raw_readl(exynos7_bts[i].va_base + ISP_QOS_CON1); break; case BTS_MFCMSCL: val_r = __raw_readl(exynos7_bts[i].va_base + MFCMSCL_QOS_CON); break; case BTS_FSYS0: val_r = __raw_readl(exynos7_bts[i].va_base + FSYS_QOS_CON0); break; case BTS_FSYS1: val_r = __raw_readl(exynos7_bts[i].va_base + FSYS_QOS_CON1); break; case BTS_FSYS2: val_r = __raw_readl(exynos7_bts[i].va_base + FSYS_QOS_CON2); break; case BTS_MODAPIF_CP: val_r = __raw_readl(exynos7_bts[i].va_base + PMUALIVE_MODAPIF_CP_QOS_CON); break; case BTS_MODAPIF_GNSS: val_r = __raw_readl(exynos7_bts[i].va_base + PMUALIVE_MODAPIF_GNSS_QOS_CON); break; case BTS_CPU: val_r = __raw_readl(exynos7_bts[i].va_base + MIF_CPU_QOS_CON); break; case BTS_APL: val_r = __raw_readl(exynos7_bts[i].va_base + MIF_APL_QOS_CON); break; default: break; } if (exynos7_bts[i].ct_ptr) bts_clk_off(exynos7_bts + i); } else { seq_puts(buf, "off\n"); } } spin_unlock(&bts_lock); return 0; } static int exynos7_qos_open(struct inode *inode, struct file *file) { return single_open(file, exynos7_qos_status_open_show, inode->i_private); } static const struct file_operations debug_qos_status_fops = { .open = exynos7_qos_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int exynos7_bw_status_open_show(struct seq_file *buf, void *d) { mutex_lock(&media_mutex); seq_printf(buf, "bts bandwidth (total %u) : decon %u, cam %u, mif_freq %u\n", total_bw, decon_bw, cam_bw, mif_freq); mutex_unlock(&media_mutex); return 0; } static int exynos7_bw_open(struct inode *inode, struct file *file) { return single_open(file, exynos7_bw_status_open_show, inode->i_private); } static const struct file_operations debug_bw_status_fops = { .open = exynos7_bw_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int debug_enable_get(void *data, unsigned long long *val) { struct bts_info *first = bts_scen[BS_DEBUG].head; struct bts_info *bts = bts_scen[BS_DEBUG].head; int cnt = 0; if (first) { do { pr_info("%s, ", bts->name); cnt++; bts = bts->table[BS_DEBUG].next_bts; } while (bts && bts != first); } if (first && first->top_scen == BS_DEBUG) pr_info("is on\n"); else pr_info("is off\n"); *val = cnt; return 0; } static int debug_enable_set(void *data, unsigned long long val) { struct bts_info *first = bts_scen[BS_DEBUG].head; struct bts_info *bts = bts_scen[BS_DEBUG].head; if (first) { do { pr_info("%s, ", bts->name); bts = bts->table[BS_DEBUG].next_bts; } while (bts && bts != first); } spin_lock(&bts_lock); if (val) { bts_add_scen(BS_DEBUG, bts_scen[BS_DEBUG].head); pr_info("is on\n"); } else { bts_del_scen(BS_DEBUG, bts_scen[BS_DEBUG].head); pr_info("is off\n"); } spin_unlock(&bts_lock); return 0; } DEFINE_SIMPLE_ATTRIBUTE(debug_enable_fops, debug_enable_get, debug_enable_set, "%llx\n"); static void bts_status_print(void) { pr_info("0 : disable debug ip\n"); pr_info("1 : BF_SETQOS\n"); pr_info("2 : BF_SETQOS_BW\n"); pr_info("3 : BF_SETQOS_MO\n"); } static int debug_ip_enable_get(void *data, unsigned long long *val) { struct bts_info *bts = data; if (bts->table[BS_DEBUG].next_scen) { switch (bts->table[BS_DEBUG].fn) { case BF_SETQOS: *val = 1; break; case BF_SETQOS_BW: *val = 2; break; case BF_SETQOS_MO: *val = 3; break; default: *val = 4; break; } } else { *val = 0; } bts_status_print(); return 0; } static int debug_ip_enable_set(void *data, unsigned long long val) { struct bts_info *bts = data; spin_lock(&bts_lock); if (val) { bts_scen[BS_DEBUG].ip |= bts->id; scen_chaining(BS_DEBUG); switch (val) { case 1: bts->table[BS_DEBUG].fn = BF_SETQOS; break; case 2: bts->table[BS_DEBUG].fn = BF_SETQOS_BW; break; case 3: bts->table[BS_DEBUG].fn = BF_SETQOS_MO; break; default: break; } bts_add_scen(BS_DEBUG, bts); pr_info("%s on 0x%x\n", bts->name, bts_scen[BS_DEBUG].ip); } else { bts->table[BS_DEBUG].next_bts = NULL; bts_del_scen(BS_DEBUG, bts); bts_scen[BS_DEBUG].ip &= ~bts->id; scen_chaining(BS_DEBUG); pr_info("%s off 0x%x\n", bts->name, bts_scen[BS_DEBUG].ip); } spin_unlock(&bts_lock); return 0; } DEFINE_SIMPLE_ATTRIBUTE(debug_ip_enable_fops, debug_ip_enable_get, debug_ip_enable_set, "%llx\n"); static int debug_ip_mo_get(void *data, unsigned long long *val) { struct bts_info *bts = data; spin_lock(&bts_lock); *val = bts->table[BS_DEBUG].mo; spin_unlock(&bts_lock); return 0; } static int debug_ip_mo_set(void *data, unsigned long long val) { struct bts_info *bts = data; spin_lock(&bts_lock); bts->table[BS_DEBUG].mo = val; if (bts->top_scen == BS_DEBUG) { if (bts->on) bts_set_ip_table(BS_DEBUG, bts); } pr_info("Debug mo set %s : mo 0x%x\n", bts->name, bts->table[BS_DEBUG].mo); spin_unlock(&bts_lock); return 0; } DEFINE_SIMPLE_ATTRIBUTE(debug_ip_mo_fops, debug_ip_mo_get, debug_ip_mo_set, "%llx\n"); static int debug_ip_token_get(void *data, unsigned long long *val) { return 0; } static int debug_ip_token_set(void *data, unsigned long long val) { return 0; } DEFINE_SIMPLE_ATTRIBUTE(debug_ip_token_fops, debug_ip_token_get, debug_ip_token_set, "%llx\n"); static int debug_ip_window_get(void *data, unsigned long long *val) { return 0; } static int debug_ip_window_set(void *data, unsigned long long val) { return 0; } DEFINE_SIMPLE_ATTRIBUTE(debug_ip_window_fops, debug_ip_window_get, debug_ip_window_set, "%llx\n"); static int debug_ip_qos_get(void *data, unsigned long long *val) { struct bts_info *bts = data; spin_lock(&bts_lock); *val = bts->table[BS_DEBUG].priority[0]; spin_unlock(&bts_lock); return 0; } static int debug_ip_qos_set(void *data, unsigned long long val) { struct bts_info *bts = data; spin_lock(&bts_lock); bts->table[BS_DEBUG].priority[0] = val; if (bts->top_scen == BS_DEBUG) { if (bts->on) bts_setqos(bts->va_base, bts->table[BS_DEBUG].priority[0]); } pr_info("Debug qos set %s : 0x%x\n", bts->name, bts->table[BS_DEBUG].priority[0]); spin_unlock(&bts_lock); return 0; } DEFINE_SIMPLE_ATTRIBUTE(debug_ip_qos_fops, debug_ip_qos_get, debug_ip_qos_set, "%llx\n"); void bts_debugfs(void) { struct bts_info *bts; struct dentry *den; struct dentry *subden; den = debugfs_create_dir("bts_dbg", NULL); debugfs_create_file("qos_status", 0440, den, NULL, &debug_qos_status_fops); debugfs_create_file("bw_status", 0440, den, NULL, &debug_bw_status_fops); debugfs_create_file("enable", 0440, den, NULL, &debug_enable_fops); den = debugfs_create_dir("bts", den); list_for_each_entry(bts, &bts_list, list) { subden = debugfs_create_dir(bts->name, den); debugfs_create_file("qos", 0644, subden, bts, &debug_ip_qos_fops); debugfs_create_file("token", 0644, subden, bts, &debug_ip_token_fops); debugfs_create_file("window", 0644, subden, bts, &debug_ip_window_fops); debugfs_create_file("mo", 0644, subden, bts, &debug_ip_mo_fops); debugfs_create_file("enable", 0644, subden, bts, &debug_ip_enable_fops); } } static void bts_drex_init(void __iomem *base) { BTS_DBG("[BTS][%s] bts drex init\n", __func__); __raw_writel(0x00000000, base + QOS_TIMEOUT_0xF); __raw_writel(0x00000004, base + QOS_TIMEOUT_0xE); __raw_writel(0x00000010, base + QOS_TIMEOUT_0xD); __raw_writel(0x00000010, base + QOS_TIMEOUT_0xC); __raw_writel(0x00000020, base + QOS_TIMEOUT_0xB); __raw_writel(0x00000040, base + QOS_TIMEOUT_0xA); __raw_writel(0x00000100, base + QOS_TIMEOUT_0x9); __raw_writel(0x00000100, base + QOS_TIMEOUT_0x8); __raw_writel(0x00000100, base + QOS_TIMEOUT_0x7); __raw_writel(0x00000100, base + QOS_TIMEOUT_0x6); __raw_writel(0x00000100, base + QOS_TIMEOUT_0x5); __raw_writel(0x00000100, base + QOS_TIMEOUT_0x4); __raw_writel(0x00000100, base + QOS_TIMEOUT_0x3); __raw_writel(0x00000100, base + QOS_TIMEOUT_0x2); __raw_writel(0x00000100, base + QOS_TIMEOUT_0x1); __raw_writel(0x00000100, base + QOS_TIMEOUT_0x0); } static void bts_initialize_domains(void) { bts_initialize("fsys", true); bts_initialize("pd-modapif", true); bts_initialize("pd-cpu", true); bts_initialize("pd-apl", true); } static int exynos_bts_notifier_event(struct notifier_block *this, unsigned long event, void *ptr) { switch (event) { case PM_POST_SUSPEND: bts_drex_init(base_drex); bts_initialize_domains(); return NOTIFY_OK; } return NOTIFY_DONE; } static struct notifier_block exynos_bts_notifier = { .notifier_call = exynos_bts_notifier_event, }; int exynos7_update_bts_param(int target_idx, int work) { return 0; } static int exynos7_bts_notify(unsigned long freq) { BUG_ON(irqs_disabled()); return srcu_notifier_call_chain(&exynos_media_notifier, freq, NULL); } int exynos7_bts_register_notifier(struct notifier_block *nb) { return srcu_notifier_chain_register(&exynos_media_notifier, nb); } int exynos7_bts_unregister_notifier(struct notifier_block *nb) { return srcu_notifier_chain_unregister(&exynos_media_notifier, nb); } void exynos7_init_bts_ioremap(void) { base_drex = ioremap(EXYNOS7870_PA_DREX0, SZ_4K); base_bts_pmu_alive = ioremap(EXYNOS7870_PA_PMU_ALIVE, SZ_4K); } void exynos_update_media_scenario(enum bts_media_type media_type, unsigned int bw, int bw_type) { mutex_lock(&media_mutex); switch (media_type) { case TYPE_DECON_INT: decon_bw = bw; num_active_disp_win = decon_bw / FHD_BW; break; case TYPE_CAM: cam_bw = bw; break; default: pr_err("DEVFREQ(MIF) : unsupportd media_type - %u", media_type); break; } total_bw = decon_bw + cam_bw; /* MIF minimum frequency calculation as per BTS guide */ if (cam_bw && decon_bw) { if (decon_bw <= (2 * FHD_BW)) mif_freq = 667000; else mif_freq = 728000; } else { if (decon_bw <= (2 * FHD_BW)) mif_freq = 416000; else mif_freq = 559000; } exynos7_bts_notify(mif_freq); BTS_DBG("[BTS BW] total: %u, decon %u, cam %u\n", total_bw, decon_bw, cam_bw); BTS_DBG("[BTS FREQ] mif_freq: %u\n", mif_freq); pm_qos_update_request(&exynos7_mif_bts_qos, mif_freq); mutex_unlock(&media_mutex); } static int __init exynos7_bts_init(void) { int i; int ret; enum bts_index btstable_index = BTS_MAX; BTS_DBG("[BTS][%s] bts init\n", __func__); for (i = 0; i < ARRAY_SIZE(clk_table); i++) { if (btstable_index != clk_table[i].index) { btstable_index = clk_table[i].index; exynos7_bts[btstable_index].ct_ptr = clk_table + i; } clk_table[i].clk = clk_get(NULL, clk_table[i].clk_name); if (IS_ERR(clk_table[i].clk)){ BTS_DBG("failed to get bts clk %s\n", clk_table[i].clk_name); exynos7_bts[btstable_index].ct_ptr = NULL; } else { ret = clk_prepare(clk_table[i].clk); if (ret) { pr_err("[BTS] failed to prepare bts clk %s\n", clk_table[i].clk_name); for (; i >= 0; i--) clk_put(clk_table[i].clk); return ret; } } } for (i = 0; i < ARRAY_SIZE(exynos7_bts); i++) { exynos7_bts[i].va_base = ioremap(exynos7_bts[i].pa_base, SZ_8K); list_add(&exynos7_bts[i].list, &bts_list); } for (i = BS_DEFAULT + 1; i < BS_MAX; i++) { scen_chaining(i); BTS_DBG("[BTS][%s] scene(%d) is chanined\n", __func__, i); } exynos7_init_bts_ioremap(); bts_drex_init(base_drex); bts_initialize_domains(); pm_qos_add_request(&exynos7_mif_bts_qos, PM_QOS_BUS_THROUGHPUT, 0); pm_qos_add_request(&exynos7_int_bts_qos, PM_QOS_DEVICE_THROUGHPUT, 0); register_pm_notifier(&exynos_bts_notifier); srcu_init_notifier_head(&exynos_media_notifier); return 0; } arch_initcall(exynos7_bts_init);