/* * 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 #include "cal_bts8890.h" #include "regs-bts.h" #define VPP_MAX 9 #define MIF_BLK_NUM 4 #define TREX_SCI_NUM 2 #define BTS_DISP (BTS_TREX_DISP0_0 | BTS_TREX_DISP0_1 | \ BTS_TREX_DISP1_0 | BTS_TREX_DISP1_1) #define BTS_MFC (BTS_TREX_MFC0 | BTS_TREX_MFC1) #define BTS_G3D (BTS_TREX_G3D0 | BTS_TREX_G3D1) #define BTS_RT (BTS_TREX_DISP0_0 | BTS_TREX_DISP0_1 | \ BTS_TREX_DISP1_0 | BTS_TREX_DISP1_1 | \ BTS_TREX_ISP0 | BTS_TREX_CAM0 | BTS_TREX_CP) #define update_rot_scen(a) (pr_state.rot_scen = a) #define update_g3d_scen(a) (pr_state.g3d_scen = a) #define update_urgent_scen(a) (pr_state.urg_scen = a) #ifdef BTS_DBGGEN #define BTS_DBG(x...) pr_err(x) #else #define BTS_DBG(x...) do {} while (0) #endif enum bts_index { BTS_IDX_TREX_DISP0_0, BTS_IDX_TREX_DISP0_1, BTS_IDX_TREX_DISP1_0, BTS_IDX_TREX_DISP1_1, BTS_IDX_TREX_ISP0, BTS_IDX_TREX_CAM0, BTS_IDX_TREX_CAM1, BTS_IDX_TREX_CP, BTS_IDX_TREX_MFC0, BTS_IDX_TREX_MFC1, BTS_IDX_TREX_G3D0, BTS_IDX_TREX_G3D1, BTS_IDX_TREX_FSYS0, BTS_IDX_TREX_FSYS1, BTS_IDX_TREX_MSCL0, BTS_IDX_TREX_MSCL1, BTS_MAX, }; #define BTS_TREX_DISP0_0 ((u64)1 << (u64)BTS_IDX_TREX_DISP0_0) #define BTS_TREX_DISP0_1 ((u64)1 << (u64)BTS_IDX_TREX_DISP0_1) #define BTS_TREX_DISP1_0 ((u64)1 << (u64)BTS_IDX_TREX_DISP1_0) #define BTS_TREX_DISP1_1 ((u64)1 << (u64)BTS_IDX_TREX_DISP1_1) #define BTS_TREX_ISP0 ((u64)1 << (u64)BTS_IDX_TREX_ISP0) #define BTS_TREX_CAM0 ((u64)1 << (u64)BTS_IDX_TREX_CAM0) #define BTS_TREX_CAM1 ((u64)1 << (u64)BTS_IDX_TREX_CAM1) #define BTS_TREX_CP ((u64)1 << (u64)BTS_IDX_TREX_CP) #define BTS_TREX_MFC0 ((u64)1 << (u64)BTS_IDX_TREX_MFC0) #define BTS_TREX_MFC1 ((u64)1 << (u64)BTS_IDX_TREX_MFC1) #define BTS_TREX_G3D0 ((u64)1 << (u64)BTS_IDX_TREX_G3D0) #define BTS_TREX_G3D1 ((u64)1 << (u64)BTS_IDX_TREX_G3D1) #define BTS_TREX_FSYS0 ((u64)1 << (u64)BTS_IDX_TREX_FSYS0) #define BTS_TREX_FSYS1 ((u64)1 << (u64)BTS_IDX_TREX_FSYS1) #define BTS_TREX_MSCL0 ((u64)1 << (u64)BTS_IDX_TREX_MSCL0) #define BTS_TREX_MSCL1 ((u64)1 << (u64)BTS_IDX_TREX_MSCL1) enum exynos_bts_scenario { BS_DISABLE, BS_DEFAULT, BS_ROTATION, BS_HIGHPERF, BS_G3DFREQ, BS_URGENTOFF, BS_DEBUG, BS_MAX, }; enum exynos_bts_function { BF_SETQOS, BF_SETQOS_BW, BF_SETQOS_MO, BF_SETQOS_FBMBW, BF_DISABLE, BF_SETTREXQOS, BF_SETTREXQOS_MO, BF_SETTREXQOS_MO_RT, BF_SETTREXQOS_MO_CP, BF_SETTREXQOS_MO_CHANGE, BF_SETTREXQOS_URGENT_OFF, BF_SETTREXQOS_BW, BF_SETTREXQOS_FBMBW, BF_SETTREXDISABLE, BF_NOP, }; enum vpp_state { VPP_BW, VPP_ROT_BW, VPP_STAT, }; struct bts_table { enum exynos_bts_function fn; unsigned int priority; unsigned int window; unsigned int token; unsigned int mo; unsigned int fbm; unsigned int mask; unsigned int timeout; unsigned int bypass_en; unsigned int decval; struct bts_info *next_bts; int prev_scen; int next_scen; }; struct bts_info { u64 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_scen_status { bool rot_scen; bool g3d_scen; bool urg_scen; }; struct bts_scenario { const char *name; u64 ip; enum exynos_bts_scenario id; struct bts_info *head; }; struct bts_scen_status pr_state = { .rot_scen = false, .g3d_scen = false, .urg_scen = false, }; struct clk_info { const char *clk_name; struct clk *clk; enum bts_index index; }; static void __iomem *base_trex[TREX_SCI_NUM]; static DEFINE_MUTEX(media_mutex); #ifdef CONFIG_EXYNOS8890_BTS_OPTIMIZATION static unsigned int prev_mo; static unsigned int vpp_rot[VPP_MAX]; #else static unsigned int vpp_bw[VPP_STAT][VPP_MAX]; static unsigned int cam_bw, sum_rot_bw, total_bw; static enum vpp_bw_type vpp_status[VPP_MAX]; static unsigned int mif_freq, int_freq; #endif static struct pm_qos_request exynos8_mif_bts_qos; static struct pm_qos_request exynos8_int_bts_qos; static struct pm_qos_request exynos8_gpu_mif_bts_qos; static struct pm_qos_request exynos8_winlayer_mif_bts_qos; static struct srcu_notifier_head exynos_media_notifier; static struct clk_info clk_table[0]; static int bts_trex_qosoff(void __iomem *base); static int bts_trex_qoson(void __iomem *base); static struct bts_info exynos8_bts[] = { [BTS_IDX_TREX_DISP0_0] = { .id = BTS_TREX_DISP0_0, .name = "disp0_0", .pd_name = "trex", .pa_base = EXYNOS8890_PA_BTS_TREX_DISP0_0, .table[BS_DEFAULT].fn = BF_SETTREXQOS_MO_RT, .table[BS_DEFAULT].priority = 0x00000008, .table[BS_DEFAULT].mo = 0x10, .table[BS_DEFAULT].timeout = 0x40, .table[BS_DEFAULT].bypass_en = 0, .table[BS_ROTATION].fn = BF_SETTREXQOS_MO_RT, .table[BS_ROTATION].priority = 0x00000008, .table[BS_ROTATION].mo = 0x20, .table[BS_ROTATION].timeout = 0x40, .table[BS_ROTATION].bypass_en = 0, .table[BS_DISABLE].fn = BF_SETTREXDISABLE, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_TREX_DISP0_1] = { .id = BTS_TREX_DISP0_1, .name = "disp0_1", .pd_name = "trex", .pa_base = EXYNOS8890_PA_BTS_TREX_DISP0_1, .table[BS_DEFAULT].fn = BF_SETTREXQOS_MO_RT, .table[BS_DEFAULT].priority = 0x00000008, .table[BS_DEFAULT].mo = 0x10, .table[BS_DEFAULT].timeout = 0x40, .table[BS_DEFAULT].bypass_en = 0, .table[BS_ROTATION].fn = BF_SETTREXQOS_MO_RT, .table[BS_ROTATION].priority = 0x00000008, .table[BS_ROTATION].mo = 0x20, .table[BS_ROTATION].timeout = 0x40, .table[BS_ROTATION].bypass_en = 0, .table[BS_DISABLE].fn = BF_SETTREXDISABLE, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_TREX_DISP1_0] = { .id = BTS_TREX_DISP1_0, .name = "disp1_0", .pd_name = "trex", .pa_base = EXYNOS8890_PA_BTS_TREX_DISP1_0, .table[BS_DEFAULT].fn = BF_SETTREXQOS_MO_RT, .table[BS_DEFAULT].priority = 0x00000008, .table[BS_DEFAULT].mo = 0x10, .table[BS_DEFAULT].timeout = 0x40, .table[BS_DEFAULT].bypass_en = 0, .table[BS_ROTATION].fn = BF_SETTREXQOS_MO_RT, .table[BS_ROTATION].priority = 0x00000008, .table[BS_ROTATION].mo = 0x20, .table[BS_ROTATION].timeout = 0x40, .table[BS_ROTATION].bypass_en = 0, .table[BS_DISABLE].fn = BF_SETTREXDISABLE, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_TREX_DISP1_1] = { .id = BTS_TREX_DISP1_1, .name = "disp1_1", .pd_name = "trex", .pa_base = EXYNOS8890_PA_BTS_TREX_DISP1_1, .table[BS_DEFAULT].fn = BF_SETTREXQOS_MO_RT, .table[BS_DEFAULT].priority = 0x00000008, .table[BS_DEFAULT].mo = 0x10, .table[BS_DEFAULT].timeout = 0x40, .table[BS_DEFAULT].bypass_en = 0, .table[BS_ROTATION].fn = BF_SETTREXQOS_MO_RT, .table[BS_ROTATION].priority = 0x00000008, .table[BS_ROTATION].mo = 0x20, .table[BS_ROTATION].timeout = 0x40, .table[BS_ROTATION].bypass_en = 0, .table[BS_DISABLE].fn = BF_SETTREXDISABLE, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_TREX_ISP0] = { .id = BTS_TREX_ISP0, .name = "isp0", .pd_name = "trex", .pa_base = EXYNOS8890_PA_BTS_TREX_ISP0, .table[BS_DEFAULT].fn = BF_SETTREXQOS_MO_RT, .table[BS_DEFAULT].priority = 0x0000000A, .table[BS_DEFAULT].mo = 0x10, .table[BS_DEFAULT].timeout = 0x10, .table[BS_DEFAULT].bypass_en = 0, .table[BS_DISABLE].fn = BF_SETTREXDISABLE, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_TREX_CAM0] = { .id = BTS_TREX_CAM0, .name = "cam0", .pd_name = "trex", .pa_base = EXYNOS8890_PA_BTS_TREX_CAM0, .table[BS_DEFAULT].fn = BF_SETTREXQOS_MO_RT, .table[BS_DEFAULT].priority = 0x0000000A, .table[BS_DEFAULT].mo = 0x10, .table[BS_DEFAULT].timeout = 0x10, .table[BS_DEFAULT].bypass_en = 0, .table[BS_DISABLE].fn = BF_SETTREXDISABLE, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_TREX_CAM1] = { .id = BTS_TREX_CAM1, .name = "cam1", .pd_name = "trex", .pa_base = EXYNOS8890_PA_BTS_TREX_CAM1, .table[BS_DEFAULT].fn = BF_SETTREXQOS_MO, .table[BS_DEFAULT].priority = 0x0000000A, .table[BS_DEFAULT].mo = 0x10, .table[BS_DEFAULT].bypass_en = 0, .table[BS_DISABLE].fn = BF_SETTREXDISABLE, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_TREX_CP] = { .id = BTS_TREX_CP, .name = "cp", .pd_name = "trex", .pa_base = EXYNOS8890_PA_BTS_TREX_CP, .table[BS_DEFAULT].fn = BF_SETTREXQOS_MO_CP, .table[BS_DEFAULT].priority = 0x0000000C, .table[BS_DEFAULT].mo = 0x10, .table[BS_DEFAULT].timeout = 0x10, .table[BS_DEFAULT].bypass_en = 0, .table[BS_URGENTOFF].fn = BF_SETTREXQOS_URGENT_OFF, .table[BS_DISABLE].fn = BF_SETTREXDISABLE, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_TREX_MFC0] = { .id = BTS_TREX_MFC0, .name = "mfc0", .pd_name = "trex", .pa_base = EXYNOS8890_PA_BTS_TREX_MFC0, .table[BS_DEFAULT].fn = BF_SETTREXQOS_MO, .table[BS_DEFAULT].priority = 0x00000004, .table[BS_DEFAULT].mo = 0x8, .table[BS_DEFAULT].bypass_en = 0, .table[BS_HIGHPERF].fn = BF_SETTREXQOS_MO_CHANGE, .table[BS_HIGHPERF].mo = 0x400, .table[BS_DISABLE].fn = BF_SETTREXDISABLE, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_TREX_MFC1] = { .id = BTS_TREX_MFC1, .name = "mfc1", .pd_name = "trex", .pa_base = EXYNOS8890_PA_BTS_TREX_MFC1, .table[BS_DEFAULT].fn = BF_SETTREXQOS_MO, .table[BS_DEFAULT].priority = 0x00000004, .table[BS_DEFAULT].mo = 0x8, .table[BS_DEFAULT].bypass_en = 0, .table[BS_HIGHPERF].fn = BF_SETTREXQOS_MO_CHANGE, .table[BS_HIGHPERF].mo = 0x400, .table[BS_DISABLE].fn = BF_SETTREXDISABLE, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_TREX_G3D0] = { .id = BTS_TREX_G3D0, .name = "g3d0", .pd_name = "trex", .pa_base = EXYNOS8890_PA_BTS_TREX_G3D0, .table[BS_DEFAULT].fn = BF_SETTREXQOS_MO, .table[BS_DEFAULT].priority = 0x00000004, .table[BS_DEFAULT].mo = 0x10, .table[BS_DEFAULT].bypass_en = 0, .table[BS_G3DFREQ].fn = BF_SETTREXQOS_MO_CHANGE, .table[BS_G3DFREQ].mo = 0x20, .table[BS_DISABLE].fn = BF_SETTREXDISABLE, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_TREX_G3D1] = { .id = BTS_TREX_G3D1, .name = "g3d1", .pd_name = "trex", .pa_base = EXYNOS8890_PA_BTS_TREX_G3D1, .table[BS_DEFAULT].fn = BF_SETTREXQOS_MO, .table[BS_DEFAULT].priority = 0x00000004, .table[BS_DEFAULT].mo = 0x10, .table[BS_DEFAULT].bypass_en = 0, .table[BS_G3DFREQ].fn = BF_SETTREXQOS_MO_CHANGE, .table[BS_G3DFREQ].mo = 0x20, .table[BS_DISABLE].fn = BF_SETTREXDISABLE, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_TREX_FSYS0] = { .id = BTS_TREX_FSYS0, .name = "fsys0", .pd_name = "trex", .pa_base = EXYNOS8890_PA_BTS_TREX_FSYS0, .table[BS_DEFAULT].fn = BF_SETTREXQOS_MO, .table[BS_DEFAULT].priority = 0x00000004, .table[BS_DEFAULT].mo = 0x4, .table[BS_DEFAULT].bypass_en = 0, .table[BS_DISABLE].fn = BF_SETTREXDISABLE, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_TREX_FSYS1] = { .id = BTS_TREX_FSYS1, .name = "fsys1", .pd_name = "trex", .pa_base = EXYNOS8890_PA_BTS_TREX_FSYS1, .table[BS_DEFAULT].fn = BF_SETTREXQOS_MO, .table[BS_DEFAULT].priority = 0x00000004, .table[BS_DEFAULT].mo = 0x4, .table[BS_DEFAULT].bypass_en = 0, .table[BS_DISABLE].fn = BF_SETTREXDISABLE, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_TREX_MSCL0] = { .id = BTS_TREX_MSCL0, .name = "mscl0", .pd_name = "trex", .pa_base = EXYNOS8890_PA_BTS_TREX_MSCL0, .table[BS_DEFAULT].fn = BF_SETTREXQOS_MO, .table[BS_DEFAULT].priority = 0x00000004, .table[BS_DEFAULT].mo = 0x4, .table[BS_DEFAULT].bypass_en = 0, .table[BS_DISABLE].fn = BF_SETTREXDISABLE, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, [BTS_IDX_TREX_MSCL1] = { .id = BTS_TREX_MSCL1, .name = "mscl1", .pd_name = "trex", .pa_base = EXYNOS8890_PA_BTS_TREX_MSCL1, .table[BS_DEFAULT].fn = BF_SETTREXQOS_MO, .table[BS_DEFAULT].priority = 0x00000004, .table[BS_DEFAULT].mo = 0x4, .table[BS_DEFAULT].bypass_en = 0, .table[BS_DISABLE].fn = BF_SETTREXDISABLE, .cur_scen = BS_DISABLE, .on = false, .enable = true, }, }; static struct bts_scenario bts_scen[] = { [BS_DISABLE] = { .name = "bts_disable", .id = BS_DISABLE, }, [BS_DEFAULT] = { .name = "bts_default", .id = BS_DEFAULT, }, [BS_ROTATION] = { .name = "bts_rotation", .ip = BTS_DISP, .id = BS_ROTATION, }, [BS_HIGHPERF] = { .name = "bts_mfchighperf", .ip = BTS_MFC, .id = BS_HIGHPERF, }, [BS_G3DFREQ] = { .name = "bts_g3dfreq", .ip = BTS_G3D, .id = BS_G3DFREQ, }, [BS_URGENTOFF] = { .name = "bts_urgentoff", .ip = BTS_TREX_CP, .id = BS_URGENTOFF, }, [BS_DEBUG] = { .name = "bts_dubugging_ip", .id = BS_DEBUG, }, [BS_MAX] = { .name = "undefined" } }; static DEFINE_SPINLOCK(bts_lock); static LIST_HEAD(bts_list); static void is_bts_clk_enabled(struct bts_info *bts) { struct clk_info *ptr; enum bts_index btstable_index; ptr = bts->ct_ptr; if (ptr) { btstable_index = ptr->index; do { if(!__clk_is_enabled(ptr->clk)) pr_err("[BTS] CLK is not enabled : %s in %s\n", ptr->clk_name, bts->name); } while (++ptr < clk_table + ARRAY_SIZE(clk_table) && ptr->index == btstable_index); } } 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_set_ip_table(enum exynos_bts_scenario scen, struct bts_info *bts) { enum exynos_bts_function fn = bts->table[scen].fn; int i; is_bts_clk_enabled(bts); 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_SETTREXQOS: bts_settrexqos(bts->va_base, bts->table[scen].priority, 0, 0); break; case BF_SETTREXQOS_MO_RT: bts_settrexqos_mo_rt(bts->va_base, bts->table[scen].priority, bts->table[scen].mo, 0, 0, bts->table[scen].timeout, bts->table[scen].bypass_en); break; case BF_SETTREXQOS_MO_CP: bts_settrexqos_mo_cp(bts->va_base, bts->table[scen].priority, bts->table[scen].mo, 0, 0, bts->table[scen].timeout, bts->table[scen].bypass_en); bts_settrexqos_urgent_on(bts->va_base); for (i = 0; i < (unsigned int)ARRAY_SIZE(base_trex); i++) bts_trex_qoson(base_trex[i]); break; case BF_SETTREXQOS_MO: bts_settrexqos_mo(bts->va_base, bts->table[scen].priority, bts->table[scen].mo, 0, 0); break; case BF_SETTREXQOS_MO_CHANGE: bts_settrexqos_mo_change(bts->va_base, bts->table[scen].mo); break; case BF_SETTREXQOS_URGENT_OFF: bts_settrexqos_urgent_off(bts->va_base); for (i = 0; i < (unsigned int)ARRAY_SIZE(base_trex); i++) bts_trex_qosoff(base_trex[i]); break; case BF_SETTREXQOS_BW: bts_settrexqos_bw(bts->va_base, bts->table[scen].priority, bts->table[scen].decval, 0, 0); break; case BF_SETTREXQOS_FBMBW: bts_settrexqos_fbmbw(bts->va_base, bts->table[scen].priority, 0, 0); break; case BF_SETTREXDISABLE: bts_trexdisable(bts->va_base, 0, 0); break; case BF_SETQOS: bts_setqos(bts->va_base, bts->table[scen].priority, 0); break; case BF_SETQOS_BW: bts_setqos_bw(bts->va_base, bts->table[scen].priority, bts->table[scen].window, bts->table[scen].token, 0); break; case BF_SETQOS_MO: bts_setqos_mo(bts->va_base, bts->table[scen].priority, bts->table[scen].mo, 0); break; case BF_SETQOS_FBMBW: bts_setqos_fbmbw(bts->va_base, bts->table[scen].priority, bts->table[scen].window, bts->table[scen].token, bts->table[scen].fbm, 0); break; case BF_DISABLE: bts_disable(bts->va_base, 0); 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_ROTATION: on = val ? true : false; scen = BS_ROTATION; bts = &exynos8_bts[BTS_IDX_TREX_DISP0_0]; BTS_DBG("[BTS] ROTATION: %s\n", bts_scen[scen].name); update_rot_scen(val); break; case TYPE_HIGHPERF: on = val ? true : false; scen = BS_HIGHPERF; bts = &exynos8_bts[BTS_IDX_TREX_MFC0]; BTS_DBG("[BTS] MFC PERF: %s\n", bts_scen[scen].name); break; case TYPE_G3D_FREQ: on = val ? true : false; scen = BS_G3DFREQ; bts = &exynos8_bts[BTS_IDX_TREX_G3D0]; BTS_DBG("[BTS] G3D FREQ: %s\n", bts_scen[scen].name); update_g3d_scen(val); break; case TYPE_URGENT_OFF: on = val ? true : false; scen = BS_URGENTOFF; bts = &exynos8_bts[BTS_IDX_TREX_CP]; BTS_DBG("[BTS] URGENT: %s\n", bts_scen[scen].name); update_urgent_scen(val); break; default: spin_unlock(&bts_lock); return; break; } 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) { BTS_DBG("[BTS] %s on/off:%d->%d\n", bts->name, bts->on, on); 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_clk_on(bts); } bts_set_ip_table(bts->top_scen, bts); } else { if (bts->on) { bts->on = false; bts_clk_off(bts); } 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 void bts_trex_init(void __iomem *base) { __raw_writel(0x0B070000, base + SCI_CTRL); __raw_writel(0x00200000, base + READ_QURGENT); __raw_writel(0x00200000, base + WRITE_QURGENT); __raw_writel(0x2A55A954, base + VC_NUM0); __raw_writel(0x00000CA0, base + VC_NUM1); __raw_writel(0x04040404, base + TH_IMM0); __raw_writel(0x04040404, base + TH_IMM1); __raw_writel(0x04040404, base + TH_IMM2); __raw_writel(0x04040404, base + TH_IMM3); __raw_writel(0x04040404, base + TH_IMM4); __raw_writel(0x04040004, base + TH_IMM5); __raw_writel(0x00040404, base + TH_IMM6); __raw_writel(0x02020202, base + TH_HIGH0); __raw_writel(0x02020202, base + TH_HIGH1); __raw_writel(0x02020202, base + TH_HIGH2); __raw_writel(0x02020202, base + TH_HIGH3); __raw_writel(0x02020202, base + TH_HIGH4); __raw_writel(0x02020002, base + TH_HIGH5); __raw_writel(0x00020202, base + TH_HIGH6); return; } static int bts_trex_qoson(void __iomem *base) { __raw_writel(0x00200000, base + READ_QURGENT); __raw_writel(0x00200000, base + WRITE_QURGENT); __raw_writel(0x04040004, base + TH_IMM5); __raw_writel(0x02020002, base + TH_HIGH5); return 0; } static int bts_trex_qosoff(void __iomem *base) { __raw_writel(0x00000000, base + READ_QURGENT); __raw_writel(0x00000000, base + WRITE_QURGENT); __raw_writel(0x04040404, base + TH_IMM5); __raw_writel(0x02020202, base + TH_HIGH5); return 0; } static int exynos_bts_notifier_event(struct notifier_block *this, unsigned long event, void *ptr) { unsigned long i; switch ((unsigned int)event) { case PM_POST_SUSPEND: for (i = 0; i < (unsigned int)ARRAY_SIZE(base_trex); i++) bts_trex_init(base_trex[i]); bts_initialize("trex", true); return NOTIFY_OK; break; case PM_SUSPEND_PREPARE: return NOTIFY_OK; break; } return NOTIFY_DONE; } static struct notifier_block exynos_bts_notifier = { .notifier_call = exynos_bts_notifier_event, }; int bts_update_gpu_mif(unsigned int freq) { int ret = 0; if (pm_qos_request_active(&exynos8_gpu_mif_bts_qos)) pm_qos_update_request(&exynos8_gpu_mif_bts_qos, freq); return ret; } #if defined(CONFIG_EXYNOS8890_BTS_OPTIMIZATION) unsigned int ip_sum_bw[IP_NUM]; unsigned int ip_peak_bw[IP_NUM]; void exynos_update_bw(enum bts_media_type ip_type, unsigned int sum_bw, unsigned int peak_bw) { unsigned int ip_sum, ip_peak; unsigned int int_freq; unsigned int mif_freq; int i; mutex_lock(&media_mutex); switch (ip_type) { case TYPE_VPP0: case TYPE_VPP1: case TYPE_VPP2: case TYPE_VPP3: case TYPE_VPP4: case TYPE_VPP5: case TYPE_VPP6: case TYPE_VPP7: case TYPE_VPP8: ip_sum_bw[IP_VPP] = sum_bw; ip_peak_bw[IP_VPP] = peak_bw; break; case TYPE_CAM: ip_sum_bw[IP_CAM] = sum_bw; ip_peak_bw[IP_CAM] = peak_bw; break; case TYPE_MFC: ip_sum_bw[IP_MFC] = sum_bw; ip_peak_bw[IP_MFC] = peak_bw; break; default: pr_err("BTS : unsupported ip type - %u", ip_type); break; } ip_sum = 0; ip_peak = ip_peak_bw[0]; for (i = 0; i < IP_NUM; i++) { ip_sum += ip_sum_bw[i]; if (ip_peak < ip_peak_bw[i]) ip_peak = ip_peak_bw[i]; } BTS_DBG("[BTS BW]: TOTAL SUM: %u, PEAK: %u\n", ip_sum, ip_peak); mif_freq = (ip_sum * 100000) / (MIF_UTIL * BUS_WIDTH); int_freq = (ip_peak * 100000) / (INT_UTIL * BUS_WIDTH); if (mif_freq < 4 * int_freq) mif_freq = 4 * int_freq; else int_freq = mif_freq / 4; BTS_DBG("[BTS FREQ]: MIF: %uMHz, INT: %uMHz\n", mif_freq, int_freq); if (pm_qos_request_active(&exynos8_mif_bts_qos)) pm_qos_update_request(&exynos8_mif_bts_qos, mif_freq); if (pm_qos_request_active(&exynos8_int_bts_qos)) pm_qos_update_request(&exynos8_int_bts_qos, int_freq); mutex_unlock(&media_mutex); } struct bts_vpp_info *exynos_bw_calc(enum bts_media_type ip_type, struct bts_hw *hw) { u64 s_ratio_h, s_ratio_v = 0; u64 s_ratio = 0; u8 df = 0; u8 bpl = 0; struct bts_vpp_info *vpp = to_bts_vpp(hw); switch (ip_type) { case TYPE_VPP0: case TYPE_VPP1: case TYPE_VPP2: case TYPE_VPP3: case TYPE_VPP4: case TYPE_VPP5: case TYPE_VPP6: case TYPE_VPP7: case TYPE_VPP8: s_ratio_h = MULTI_FACTOR * vpp->src_w / vpp->dst_w; s_ratio_v = MULTI_FACTOR * vpp->src_h / vpp->dst_h; s_ratio = PIXEL_BUFFER * s_ratio_h * s_ratio_v / (MULTI_FACTOR * MULTI_FACTOR); bpl = vpp->bpp * 10 / 8; if (vpp->bpp == 32) df = 20; else if (vpp->bpp == 12) df = 36; else if (vpp->bpp == 16) df = 28; vpp->cur_bw = vpp->src_h * vpp->src_w * bpl * s_ratio_h * s_ratio_v * 72 / (MULTI_FACTOR * MULTI_FACTOR * 10) / (MULTI_FACTOR * MULTI_FACTOR); if (vpp->is_rotation) vpp->peak_bw = (s_ratio + df * vpp->src_h) * bpl * PEAK_FACTOR * (FPS * vpp->dst_h) / VBI_FACTOR / 100 / MULTI_FACTOR; else vpp->peak_bw = (s_ratio + 4 * vpp->src_w + 1000) * bpl * PEAK_FACTOR * (FPS * vpp->dst_h) / VBI_FACTOR / 100 / MULTI_FACTOR; BTS_DBG("%s[%i] cur_bw: %llu peak_bw: %llu\n", __func__, (int)ip_type, vpp->cur_bw, vpp->peak_bw); break; case TYPE_CAM: case TYPE_MFC: default: pr_err("%s: BW calculation unsupported - %u", __func__, ip_type); break; } return vpp; } void bts_ext_scenario_set(enum bts_media_type ip_type, enum bts_scen_type scen_type, bool on) { int i = 0; unsigned int cur_mo; switch (ip_type) { case TYPE_VPP0: case TYPE_VPP1: case TYPE_VPP2: case TYPE_VPP3: case TYPE_VPP4: case TYPE_VPP5: case TYPE_VPP6: case TYPE_VPP7: case TYPE_VPP8: if (scen_type == TYPE_ROTATION) { if (on) vpp_rot[ip_type - TYPE_VPP0] = 16; else vpp_rot[ip_type - TYPE_VPP0] = 8; cur_mo = 8; for (i = 0; i < VPP_MAX; i++) { if (cur_mo < vpp_rot[i]) cur_mo = vpp_rot[i]; } if (cur_mo == 16 && cur_mo != prev_mo) { bts_scen_update(scen_type, 1); prev_mo = 16; } else if (cur_mo == prev_mo) { return; } else { bts_scen_update(scen_type, 0); prev_mo = 8; } } break; case TYPE_CAM: if (scen_type == TYPE_URGENT_OFF) { if (on) bts_scen_update(scen_type, 1); else bts_scen_update(scen_type, 0); } break; case TYPE_MFC: if (scen_type == TYPE_HIGHPERF) { if (on) bts_scen_update(scen_type, 1); else bts_scen_update(scen_type, 0); } break; case TYPE_G3D: if (scen_type == TYPE_G3D_FREQ) { if (on) bts_scen_update(scen_type, 1); else bts_scen_update(scen_type, 0); } break; default: pr_err("BTS : unsupported rotation ip - %u", ip_type); break; } return; } int bts_update_winlayer(unsigned int layers) { unsigned int freq = 0; switch (layers) { case 0: case 1: case 2: case 3: case 4: freq = 0; break; case 5: freq = 676000; break; case 6: freq = 845000; break; case 7: case 8: freq = 1014000; break; default: break; } if (pm_qos_request_active(&exynos8_winlayer_mif_bts_qos)) pm_qos_update_request(&exynos8_winlayer_mif_bts_qos, freq); return 0; } #else /* BTS_OPTIMIZATION */ void exynos_update_media_scenario(enum bts_media_type media_type, unsigned int bw, int bw_type) { unsigned int vpp_total_bw = 0; unsigned int vpp_bus_bw[4]; unsigned int vpp_bus_max_bw; int utilization; int i; int max_status = 0; static unsigned int is_yuv; static unsigned int prev_sum_rot_bw; static unsigned int mif_ud_encoding, mif_ud_decoding; mutex_lock(&media_mutex); switch (media_type) { case TYPE_VPP0: case TYPE_VPP1: case TYPE_VPP2: case TYPE_VPP3: case TYPE_VPP4: case TYPE_VPP5: case TYPE_VPP6: case TYPE_VPP7: case TYPE_VPP8: vpp_bw[VPP_BW][media_type - TYPE_VPP0] = bw; vpp_status[media_type - TYPE_VPP0] = bw_type; if (bw_type) vpp_bw[VPP_ROT_BW][media_type - TYPE_VPP0] = bw; else vpp_bw[VPP_ROT_BW][media_type - TYPE_VPP0] = 0; /* * VPP0(G0), VPP1(G1), VPP2(VG0), VPP3(VG1), * VPP4(G2), VPP5(G3), VPP6(VGR0), VPP7(VGR1), VPP8(WB) * vpp_bus_bw[0] = DISP0_0_BW = G0 + VG0; * vpp_bus_bw[1] = DISP0_1_BW = G1 + VG1; * vpp_bus_bw[2] = DISP1_0_BW = G2 + VGR0; * vpp_bus_bw[3] = DISP1_1_BW = G3 + VGR1 + WB; */ vpp_bus_bw[0] = vpp_bw[VPP_BW][0] + vpp_bw[VPP_BW][2]; vpp_bus_bw[1] = vpp_bw[VPP_BW][1] + vpp_bw[VPP_BW][3]; vpp_bus_bw[2] = vpp_bw[VPP_BW][4] + vpp_bw[VPP_BW][6]; vpp_bus_bw[3] = vpp_bw[VPP_BW][5] + vpp_bw[VPP_BW][7] + vpp_bw[VPP_BW][8]; /* sum VPP rotation BW for RT BW */ sum_rot_bw = 0; for (i = 0; i < VPP_MAX; i++) sum_rot_bw += vpp_bw[VPP_ROT_BW][i]; /* Such VPP max BW for INT PM QoS */ vpp_bus_max_bw = vpp_bus_bw[0]; for (i = 0; i < 4; i++) { if (vpp_bus_max_bw < vpp_bus_bw[i]) vpp_bus_max_bw = vpp_bus_bw[i]; } prev_sum_rot_bw = sum_rot_bw; break; case TYPE_CAM: cam_bw = bw; break; case TYPE_YUV: is_yuv = bw; break; case TYPE_UD_ENC: mif_ud_encoding = bw; break; case TYPE_UD_DEC: mif_ud_decoding = bw; break; default: pr_err("BTS : unsupported media type - %u", media_type); break; } for (i = 0; i < VPP_MAX; i++) vpp_total_bw += vpp_bw[VPP_BW][i]; total_bw = vpp_total_bw + cam_bw; int_freq = (total_bw * 100) / (INT_UTIL * BUS_WIDTH); /* INT_L0 shared between camera and normal scenarioes */ if (int_freq > 468000) int_freq = 690000; if (pm_qos_request_active(&exynos8_int_bts_qos)) pm_qos_update_request(&exynos8_int_bts_qos, int_freq); for (i = 0; i < VPP_MAX; i++) { if (max_status < vpp_status[i]) max_status = vpp_status[i]; } switch (max_status) { case BW_FULLHD_ROT: SIZE_FACTOR(total_bw); case BW_ROT: if (total_bw < 200000) utilization = 7; else if (total_bw < 500000) utilization = 10; else if (total_bw < 1600000) utilization = 29; else if (total_bw < 3000000) utilization = 48; else if (total_bw < 3500000) utilization = 55; else if (total_bw < 4000000) utilization = 60; else if (total_bw < 5000000) utilization = 70; else if (total_bw < 7000000) utilization = 76; else utilization = 83; break; case BW_DEFAULT: if (total_bw < (63000 * 2) * 8 * 4) utilization = MIF_UTIL; else utilization = MIF_UTIL2; break; default: pr_err("BTS : unsupported bandwidth type - %u", media_type); break; } mif_freq = (total_bw * 100) / (utilization * BUS_WIDTH); /* for MFC UHD en/decoding (TBD) */ if (mif_ud_encoding && mif_freq < MIF_ENCODING) mif_freq = MIF_ENCODING; if (mif_ud_decoding && mif_freq < MIF_DECODING) mif_freq = MIF_DECODING; BTS_DBG("[BTS BW] total: %u, vpp: %u, vpp_rot: %u, cam: %u, yuv: %u, ud_en: %u, ud_de:%u\n", total_bw, vpp_total_bw, sum_rot_bw, cam_bw, is_yuv, mif_ud_encoding, mif_ud_decoding); /* * VPP0(G0), VPP1(G1), VPP2(VG0), VPP3(VG1), * VPP4(G2), VPP5(G3), VPP6(VGR0), VPP7(VGR1), VPP8(WB) * vpp_bus_bw[0] = DISP0_0_BW = G0 + VG0; * vpp_bus_bw[1] = DISP0_1_BW = G1 + VG1; * vpp_bus_bw[2] = DISP1_0_BW = G2 + VGR0; * vpp_bus_bw[3] = DISP1_1_BW = G3 + VGR1 + WB; */ BTS_DBG("[BTS VPP] vpp0(G0) vpp1(G1) vpp2(VG0) vpp3(VG1) vpp4(G2) vpp5(G3) " "vpp6(VGR0) vpp7(VGR1) vpp8(WB)\n"); BTS_DBG("[BTS VPP]"); for (i = 0; i < VPP_MAX; i++) BTS_DBG(" vpp%d %u,", i, vpp_bw[VPP_BW][i]); BTS_DBG("\n"); BTS_DBG("[BTS FREQ] MIF freq: %u, util: %u, INT freq: %u\n", mif_freq, utilization, int_freq); if (pm_qos_request_active(&exynos8_mif_bts_qos)) pm_qos_update_request(&exynos8_mif_bts_qos, mif_freq); mutex_unlock(&media_mutex); } #endif /* BTS_OPT */ static void __iomem *sci_base; void exynos_bts_scitoken_setting(bool on) { if (on) __raw_writel(0x10101117, sci_base + CMDTOKEN); else __raw_writel(0x10101127, sci_base + CMDTOKEN); } #ifdef CONFIG_CPU_IDLE static int exynos8_bts_lpa_event(struct notifier_block *nb, unsigned long event, void *data) { unsigned long i; switch (event) { case LPA_EXIT: for (i = 0; i < (unsigned int)ARRAY_SIZE(base_trex); i++) bts_trex_init(base_trex[i]); bts_initialize("trex", true); break; case LPA_ENTER: break; default: break; } return NOTIFY_DONE; } static struct notifier_block bts_lpa_nb = { .notifier_call = exynos8_bts_lpa_event, }; #endif static int __init exynos8_bts_init(void) { signed long i; int ret; enum bts_index btstable_index = BTS_MAX - 1; 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; exynos8_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); exynos8_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(exynos8_bts); i++) { exynos8_bts[i].va_base = ioremap(exynos8_bts[i].pa_base, SZ_16K); list_add(&exynos8_bts[i].list, &bts_list); } base_trex[0] = ioremap(EXYNOS8_PA_TREX_CCORE0, SZ_4K); base_trex[1] = ioremap(EXYNOS8_PA_TREX_CCORE1, SZ_4K); for (i = BS_DEFAULT + 1; i < BS_MAX; i++) scen_chaining(i); for (i = 0; i < ARRAY_SIZE(base_trex); i++) bts_trex_init(base_trex[i]); bts_initialize("trex", true); /* SCI Related settings */ sci_base = ioremap(EXYNOS8_PA_SCI, SZ_4K); exynos_bts_scitoken_setting(false); pm_qos_add_request(&exynos8_mif_bts_qos, PM_QOS_BUS_THROUGHPUT, 0); pm_qos_add_request(&exynos8_int_bts_qos, PM_QOS_DEVICE_THROUGHPUT, 0); pm_qos_add_request(&exynos8_gpu_mif_bts_qos, PM_QOS_BUS_THROUGHPUT, 0); pm_qos_add_request(&exynos8_winlayer_mif_bts_qos, PM_QOS_BUS_THROUGHPUT, 0); register_pm_notifier(&exynos_bts_notifier); //bts_debugfs(); srcu_init_notifier_head(&exynos_media_notifier); #ifdef CONFIG_CPU_IDLE exynos_pm_register_notifier(&bts_lpa_nb); #endif return 0; } arch_initcall(exynos8_bts_init);