| #include <linux/types.h> |
| #include <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/io.h> |
| #include <linux/debugfs.h> |
| #include <linux/uaccess.h> |
| #include <linux/kobject.h> |
| #include <soc/samsung/cal-if.h> |
| |
| #include "fvmap.h" |
| #include "cmucal.h" |
| #include "vclk.h" |
| #include "ra.h" |
| |
| #define FVMAP_SIZE (SZ_8K) |
| #define STEP_UV (6250) |
| |
| void __iomem *fvmap_base; |
| void __iomem *sram_fvmap_base; |
| |
| static int init_margin_table[MAX_MARGIN_ID]; |
| static int volt_offset_percent = 0; |
| static int percent_margin_table[MAX_MARGIN_ID]; |
| |
| static int __init get_mif_volt(char *str) |
| { |
| int volt; |
| |
| get_option(&str, &volt); |
| init_margin_table[MARGIN_MIF] = volt; |
| |
| return 0; |
| } |
| early_param("mif", get_mif_volt); |
| |
| static int __init get_int_volt(char *str) |
| { |
| int volt; |
| |
| get_option(&str, &volt); |
| init_margin_table[MARGIN_INT] = volt; |
| |
| return 0; |
| } |
| early_param("int", get_int_volt); |
| |
| static int __init get_big_volt(char *str) |
| { |
| int volt; |
| |
| get_option(&str, &volt); |
| init_margin_table[MARGIN_BIG] = volt; |
| |
| return 0; |
| } |
| early_param("big", get_big_volt); |
| |
| static int __init get_mid_volt(char *str) |
| { |
| int volt; |
| |
| get_option(&str, &volt); |
| init_margin_table[MARGIN_MID] = volt; |
| |
| return 0; |
| } |
| early_param("mid", get_mid_volt); |
| |
| static int __init get_lit_volt(char *str) |
| { |
| int volt; |
| |
| get_option(&str, &volt); |
| init_margin_table[MARGIN_LIT] = volt; |
| |
| return 0; |
| } |
| early_param("lit", get_lit_volt); |
| |
| static int __init get_g3d_volt(char *str) |
| { |
| int volt; |
| |
| get_option(&str, &volt); |
| init_margin_table[MARGIN_G3D] = volt; |
| |
| return 0; |
| } |
| early_param("g3d", get_g3d_volt); |
| |
| static int __init get_intcam_volt(char *str) |
| { |
| int volt; |
| |
| get_option(&str, &volt); |
| init_margin_table[MARGIN_INTCAM] = volt; |
| |
| return 0; |
| } |
| early_param("intcam", get_intcam_volt); |
| |
| static int __init get_cam_volt(char *str) |
| { |
| int volt; |
| |
| get_option(&str, &volt); |
| init_margin_table[MARGIN_CAM] = volt; |
| |
| return 0; |
| } |
| early_param("cam", get_cam_volt); |
| |
| static int __init get_disp_volt(char *str) |
| { |
| int volt; |
| |
| get_option(&str, &volt); |
| init_margin_table[MARGIN_DISP] = volt; |
| |
| return 0; |
| } |
| early_param("disp", get_disp_volt); |
| |
| static int __init get_g3dm_volt(char *str) |
| { |
| int volt; |
| |
| get_option(&str, &volt); |
| init_margin_table[MARGIN_G3DM] = volt; |
| |
| return 0; |
| } |
| early_param("g3dm", get_g3dm_volt); |
| |
| static int __init get_cp_volt(char *str) |
| { |
| int volt; |
| |
| get_option(&str, &volt); |
| init_margin_table[MARGIN_CP] = volt; |
| |
| return 0; |
| } |
| early_param("cp", get_cp_volt); |
| |
| static int __init get_fsys0_volt(char *str) |
| { |
| int volt; |
| |
| get_option(&str, &volt); |
| init_margin_table[MARGIN_FSYS0] = volt; |
| |
| return 0; |
| } |
| early_param("fsys0", get_fsys0_volt); |
| |
| static int __init get_aud_volt(char *str) |
| { |
| int volt; |
| |
| get_option(&str, &volt); |
| init_margin_table[MARGIN_AUD] = volt; |
| |
| return 0; |
| } |
| early_param("aud", get_aud_volt); |
| |
| static int __init get_iva_volt(char *str) |
| { |
| int volt; |
| |
| get_option(&str, &volt); |
| init_margin_table[MARGIN_IVA] = volt; |
| |
| return 0; |
| } |
| early_param("iva", get_iva_volt); |
| |
| static int __init get_score_volt(char *str) |
| { |
| int volt; |
| |
| get_option(&str, &volt); |
| init_margin_table[MARGIN_SCORE] = volt; |
| |
| return 0; |
| } |
| early_param("score", get_score_volt); |
| |
| static int __init get_npu_volt(char *str) |
| { |
| int volt; |
| |
| get_option(&str, &volt); |
| init_margin_table[MARGIN_NPU] = volt; |
| |
| return 0; |
| } |
| early_param("npu", get_npu_volt); |
| |
| static int __init get_mfc_volt(char *str) |
| { |
| int volt; |
| |
| get_option(&str, &volt); |
| init_margin_table[MARGIN_MFC] = volt; |
| |
| return 0; |
| } |
| early_param("mfc", get_mfc_volt); |
| |
| static int __init get_percent_margin_volt(char *str) |
| { |
| int percent; |
| |
| get_option(&str, &percent); |
| volt_offset_percent = percent; |
| |
| return 0; |
| } |
| early_param("volt_offset_percent", get_percent_margin_volt); |
| |
| int fvmap_set_raw_voltage_table(unsigned int id, int uV) |
| { |
| struct fvmap_header *fvmap_header; |
| struct rate_volt_header *fv_table; |
| int num_of_lv; |
| int idx, i; |
| |
| idx = GET_IDX(id); |
| |
| fvmap_header = sram_fvmap_base; |
| fv_table = sram_fvmap_base + fvmap_header[idx].o_ratevolt; |
| num_of_lv = fvmap_header[idx].num_of_lv; |
| |
| for (i = 0; i < num_of_lv; i++) |
| fv_table->table[i].volt += uV; |
| |
| return 0; |
| } |
| |
| int fvmap_get_voltage_table(unsigned int id, unsigned int *table) |
| { |
| struct fvmap_header *fvmap_header = fvmap_base; |
| struct rate_volt_header *fv_table; |
| int idx, i; |
| int num_of_lv; |
| |
| if (!IS_ACPM_VCLK(id)) |
| return 0; |
| |
| idx = GET_IDX(id); |
| |
| fvmap_header = fvmap_base; |
| fv_table = fvmap_base + fvmap_header[idx].o_ratevolt; |
| num_of_lv = fvmap_header[idx].num_of_lv; |
| |
| for (i = 0; i < num_of_lv; i++) |
| table[i] = fv_table->table[i].volt; |
| |
| return num_of_lv; |
| |
| } |
| |
| int fvmap_get_raw_voltage_table(unsigned int id) |
| { |
| struct fvmap_header *fvmap_header; |
| struct rate_volt_header *fv_table; |
| int idx, i; |
| int num_of_lv; |
| unsigned int table[20]; |
| |
| idx = GET_IDX(id); |
| |
| fvmap_header = sram_fvmap_base; |
| fv_table = sram_fvmap_base + fvmap_header[idx].o_ratevolt; |
| num_of_lv = fvmap_header[idx].num_of_lv; |
| |
| for (i = 0; i < num_of_lv; i++) |
| table[i] = fv_table->table[i].volt; |
| |
| for (i = 0; i < num_of_lv; i++) |
| printk("dvfs id : %d %d Khz : %d uv\n", ACPM_VCLK_TYPE | id, fv_table->table[i].rate, table[i]); |
| |
| return 0; |
| } |
| |
| static void check_percent_margin(struct rate_volt_header *head, unsigned int num_of_lv) |
| { |
| int org_volt; |
| int percent_volt; |
| int i; |
| |
| if (!volt_offset_percent) |
| return; |
| |
| for (i = 0; i < num_of_lv; i++) { |
| org_volt = head->table[i].volt; |
| percent_volt = org_volt * volt_offset_percent / 100; |
| head->table[i].volt = org_volt + rounddown(percent_volt, STEP_UV); |
| } |
| } |
| |
| static int get_vclk_id_from_margin_id(int margin_id) |
| { |
| int size = cmucal_get_list_size(ACPM_VCLK_TYPE); |
| int i; |
| struct vclk *vclk; |
| |
| for (i = 0; i < size; i++) { |
| vclk = cmucal_get_node(ACPM_VCLK_TYPE | i); |
| |
| if (vclk->margin_id == margin_id) |
| return i; |
| } |
| |
| return -EINVAL; |
| } |
| |
| #define attr_percent(margin_id, type) \ |
| static ssize_t show_##type##_percent \ |
| (struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ |
| { \ |
| return snprintf(buf, PAGE_SIZE, "%d\n", percent_margin_table[margin_id]); \ |
| } \ |
| \ |
| static ssize_t store_##type##_percent \ |
| (struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) \ |
| { \ |
| int input, vclk_id; \ |
| \ |
| if (!sscanf(buf, "%d", &input)) \ |
| return -EINVAL; \ |
| \ |
| if (input < -100 || input > 100) \ |
| return -EINVAL; \ |
| \ |
| vclk_id = get_vclk_id_from_margin_id(margin_id); \ |
| if (vclk_id == -EINVAL) \ |
| return vclk_id; \ |
| percent_margin_table[margin_id] = input; \ |
| cal_dfs_set_volt_margin(vclk_id | ACPM_VCLK_TYPE, input); \ |
| \ |
| return count; \ |
| } \ |
| \ |
| static struct kobj_attribute type##_percent = \ |
| __ATTR(type##_percent, 0600, \ |
| show_##type##_percent, store_##type##_percent) |
| |
| attr_percent(MARGIN_MIF, mif_margin); |
| attr_percent(MARGIN_INT, int_margin); |
| attr_percent(MARGIN_BIG, big_margin); |
| attr_percent(MARGIN_MID, mid_margin); |
| attr_percent(MARGIN_LIT, lit_margin); |
| attr_percent(MARGIN_G3D, g3d_margin); |
| attr_percent(MARGIN_INTCAM, intcam_margin); |
| attr_percent(MARGIN_CAM, cam_margin); |
| attr_percent(MARGIN_DISP, disp_margin); |
| attr_percent(MARGIN_CP, cp_margin); |
| attr_percent(MARGIN_FSYS0, fsys0_margin); |
| attr_percent(MARGIN_AUD, aud_margin); |
| attr_percent(MARGIN_IVA, iva_margin); |
| attr_percent(MARGIN_SCORE, score_margin); |
| attr_percent(MARGIN_NPU, npu_margin); |
| attr_percent(MARGIN_MFC, mfc_margin); |
| |
| static struct attribute *percent_margin_attrs[] = { |
| &mif_margin_percent.attr, |
| &int_margin_percent.attr, |
| &big_margin_percent.attr, |
| &mid_margin_percent.attr, |
| &lit_margin_percent.attr, |
| &g3d_margin_percent.attr, |
| &intcam_margin_percent.attr, |
| &cam_margin_percent.attr, |
| &disp_margin_percent.attr, |
| &cp_margin_percent.attr, |
| &fsys0_margin_percent.attr, |
| &aud_margin_percent.attr, |
| &iva_margin_percent.attr, |
| &score_margin_percent.attr, |
| &npu_margin_percent.attr, |
| &mfc_margin_percent.attr, |
| NULL, |
| }; |
| |
| static const struct attribute_group percent_margin_group = { |
| .attrs = percent_margin_attrs, |
| }; |
| |
| static void fvmap_copy_from_sram(void __iomem *map_base, void __iomem *sram_base) |
| { |
| volatile struct fvmap_header *fvmap_header, *header; |
| struct rate_volt_header *old, *new; |
| struct dvfs_table *old_param, *new_param; |
| struct clocks *clks; |
| struct pll_header *plls; |
| struct vclk *vclk; |
| unsigned int member_addr; |
| unsigned int blk_idx, param_idx; |
| int size, margin; |
| int i, j, k; |
| |
| fvmap_header = map_base; |
| header = sram_base; |
| |
| size = cmucal_get_list_size(ACPM_VCLK_TYPE); |
| |
| for (i = 0; i < size; i++) { |
| /* load fvmap info */ |
| fvmap_header[i].dvfs_type = header[i].dvfs_type; |
| fvmap_header[i].num_of_lv = header[i].num_of_lv; |
| fvmap_header[i].num_of_members = header[i].num_of_members; |
| fvmap_header[i].num_of_pll = header[i].num_of_pll; |
| fvmap_header[i].num_of_mux = header[i].num_of_mux; |
| fvmap_header[i].num_of_div = header[i].num_of_div; |
| fvmap_header[i].gearratio = header[i].gearratio; |
| fvmap_header[i].init_lv = header[i].init_lv; |
| fvmap_header[i].num_of_gate = header[i].num_of_gate; |
| fvmap_header[i].reserved[0] = header[i].reserved[0]; |
| fvmap_header[i].reserved[1] = header[i].reserved[1]; |
| fvmap_header[i].block_addr[0] = header[i].block_addr[0]; |
| fvmap_header[i].block_addr[1] = header[i].block_addr[1]; |
| fvmap_header[i].block_addr[2] = header[i].block_addr[2]; |
| fvmap_header[i].o_members = header[i].o_members; |
| fvmap_header[i].o_ratevolt = header[i].o_ratevolt; |
| fvmap_header[i].o_tables = header[i].o_tables; |
| |
| vclk = cmucal_get_node(ACPM_VCLK_TYPE | i); |
| if (vclk == NULL) |
| continue; |
| pr_info("dvfs_type : %s - id : %x\n", |
| vclk->name, fvmap_header[i].dvfs_type); |
| pr_info(" num_of_lv : %d\n", fvmap_header[i].num_of_lv); |
| pr_info(" num_of_members : %d\n", fvmap_header[i].num_of_members); |
| |
| old = sram_base + fvmap_header[i].o_ratevolt; |
| new = map_base + fvmap_header[i].o_ratevolt; |
| |
| check_percent_margin(old, fvmap_header[i].num_of_lv); |
| |
| margin = init_margin_table[vclk->margin_id]; |
| if (margin) |
| cal_dfs_set_volt_margin(i | ACPM_VCLK_TYPE, margin); |
| |
| for (j = 0; j < fvmap_header[i].num_of_members; j++) { |
| clks = sram_base + fvmap_header[i].o_members; |
| |
| if (j < fvmap_header[i].num_of_pll) { |
| plls = sram_base + clks->addr[j]; |
| member_addr = plls->addr - 0x90000000; |
| } else { |
| |
| member_addr = (clks->addr[j] & ~0x3) & 0xffff; |
| blk_idx = clks->addr[j] & 0x3; |
| |
| if (blk_idx < BLOCK_ADDR_SIZE) |
| member_addr |= ((fvmap_header[i].block_addr[blk_idx]) << 16) - 0x90000000; |
| else |
| pr_err("[%s] blk_idx %u is out of range for block_addr\n", __func__, blk_idx); |
| } |
| |
| |
| vclk->list[j] = cmucal_get_id_by_addr(member_addr); |
| |
| if (vclk->list[j] == INVALID_CLK_ID) |
| pr_info(" Invalid addr :0x%x\n", member_addr); |
| else |
| pr_info(" DVFS CMU addr:0x%x\n", member_addr); |
| } |
| |
| for (j = 0; j < fvmap_header[i].num_of_lv; j++) { |
| new->table[j].rate = old->table[j].rate; |
| new->table[j].volt = old->table[j].volt; |
| pr_info(" lv : [%7d], volt = %d uV (%d %%) \n", |
| new->table[j].rate, new->table[j].volt, |
| volt_offset_percent); |
| } |
| |
| old_param = sram_base + fvmap_header[i].o_tables; |
| new_param = map_base + fvmap_header[i].o_tables; |
| for (j = 0; j < fvmap_header[i].num_of_lv; j++) { |
| for (k = 0; k < fvmap_header[i].num_of_members; k++) { |
| param_idx = fvmap_header[i].num_of_members * j + k; |
| new_param->val[param_idx] = old_param->val[param_idx]; |
| if (vclk->lut[j].params[k] != new_param->val[param_idx]) { |
| vclk->lut[j].params[k] = new_param->val[param_idx]; |
| pr_info("Mis-match %s[%d][%d] : %d %d\n", |
| vclk->name, j, k, |
| vclk->lut[j].params[k], |
| new_param->val[param_idx]); |
| } |
| } |
| } |
| } |
| } |
| |
| int fvmap_init(void __iomem *sram_base) |
| { |
| void __iomem *map_base; |
| struct kobject *kobj; |
| |
| map_base = kzalloc(FVMAP_SIZE, GFP_KERNEL); |
| |
| fvmap_base = map_base; |
| sram_fvmap_base = sram_base; |
| pr_info("%s:fvmap initialize %pK\n", __func__, sram_base); |
| fvmap_copy_from_sram(map_base, sram_base); |
| |
| /* percent margin for each doamin at runtime */ |
| kobj = kobject_create_and_add("percent_margin", power_kobj); |
| if (!kobj) |
| pr_err("Fail to create percent_margin kboject\n"); |
| |
| if (sysfs_create_group(kobj, &percent_margin_group)) |
| pr_err("Fail to create percent_margin group\n"); |
| |
| return 0; |
| } |