/* linux/arch/arm/mach-exynos/busfreq_opp_4210.c * * Copyright (c) 2011 Samsung Electronics Co., Ltd. * http://www.samsung.com/ * * EXYNOS4 - BUS clock frequency scaling support with OPP * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum busfreq_level_idx { LV_0, LV_1, LV_2, LV_END }; static struct busfreq_table exynos4_busfreq_table[] = { {LV_0, 400000, 1100000, 0, 0, 0}, {LV_1, 267000, 1000000, 0, 0, 0}, {LV_2, 133000, 950000, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, }; #define ASV_GROUP 5 static unsigned int exynos4_asv_volt[ASV_GROUP][LV_END] = { {1150000, 1050000, 1000000}, {1125000, 1025000, 1000000}, {1100000, 1000000, 975000}, {1075000, 975000, 950000}, {1050000, 950000, 950000}, }; static unsigned int clkdiv_dmc0[LV_END][8] = { /* * Clock divider value for following * { DIVACP, DIVACP_PCLK, DIVDPHY, DIVDMC, DIVDMCD * DIVDMCP, DIVCOPY2, DIVCORE_TIMERS } */ /* DMC L0: 400MHz */ {3, 1, 1, 1, 1, 1, 3, 1}, /* DMC L1: 266.7MHz */ {4, 1, 1, 2, 1, 1, 3, 1}, /* DMC L2: 133MHz */ {5, 1, 1, 5, 1, 1, 3, 1}, }; static unsigned int clkdiv_top[LV_END][5] = { /* * Clock divider value for following * { DIVACLK200, DIVACLK100, DIVACLK160, DIVACLK133, DIVONENAND } */ /* ACLK200 L0: 200MHz */ {3, 7, 4, 5, 1}, /* ACLK200 L1: 160MHz */ {4, 7, 5, 6, 1}, /* ACLK200 L2: 133MHz */ {5, 7, 7, 7, 1}, }; static unsigned int clkdiv_lr_bus[LV_END][2] = { /* * Clock divider value for following * { DIVGDL/R, DIVGPL/R } */ /* ACLK_GDL/R L1: 200MHz */ {3, 1}, /* ACLK_GDL/R L2: 160MHz */ {4, 1}, /* ACLK_GDL/R L3: 133MHz */ {5, 1}, }; static void exynos4210_set_bus_volt(void) { unsigned int asv_group; unsigned int i; asv_group = exynos_asv->asv_result & 0xF; asv_group = 0; printk(KERN_INFO "DVFS : VDD_INT Voltage table set with %d Group\n", asv_group); for (i = 0 ; i < LV_END ; i++) { switch (asv_group) { case 0: exynos4_busfreq_table[i].volt = exynos4_asv_volt[0][i]; break; case 1: case 2: exynos4_busfreq_table[i].volt = exynos4_asv_volt[1][i]; break; case 3: case 4: exynos4_busfreq_table[i].volt = exynos4_asv_volt[2][i]; break; case 5: case 6: exynos4_busfreq_table[i].volt = exynos4_asv_volt[3][i]; break; case 7: exynos4_busfreq_table[i].volt = exynos4_asv_volt[4][i]; break; } } return; } unsigned int exynos4210_target(unsigned int div_index) { unsigned int tmp; /* Change Divider - DMC0 */ tmp = exynos4_busfreq_table[div_index].clk_dmc0div; __raw_writel(tmp, EXYNOS4_CLKDIV_DMC0); do { tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_DMC0); } while (tmp & 0x11111111); /* Change Divider - TOP */ tmp = exynos4_busfreq_table[div_index].clk_topdiv; __raw_writel(tmp, EXYNOS4_CLKDIV_TOP); do { tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_TOP); } while (tmp & 0x11111); /* Change Divider - LEFTBUS */ tmp = __raw_readl(EXYNOS4_CLKDIV_LEFTBUS); tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK); tmp |= ((clkdiv_lr_bus[div_index][0] << EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) | (clkdiv_lr_bus[div_index][1] << EXYNOS4_CLKDIV_BUS_GPLR_SHIFT)); __raw_writel(tmp, EXYNOS4_CLKDIV_LEFTBUS); do { tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_LEFTBUS); } while (tmp & 0x11); /* Change Divider - RIGHTBUS */ tmp = __raw_readl(EXYNOS4_CLKDIV_RIGHTBUS); tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK); tmp |= ((clkdiv_lr_bus[div_index][0] << EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) | (clkdiv_lr_bus[div_index][1] << EXYNOS4_CLKDIV_BUS_GPLR_SHIFT)); __raw_writel(tmp, EXYNOS4_CLKDIV_RIGHTBUS); do { tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_RIGHTBUS); } while (tmp & 0x11); return div_index; } unsigned int exynos4210_get_table_index(struct opp *opp) { unsigned int index; for (index = LV_0; index < LV_END; index++) if (opp_get_freq(opp) == exynos4_busfreq_table[index].mem_clk) break; return index; } int exynos4210_init(struct device *dev, struct busfreq_data *data) { unsigned int i; unsigned int tmp; unsigned long maxfreq = UINT_MAX; int ret; tmp = __raw_readl(EXYNOS4_CLKDIV_DMC0); for (i = 0; i < LV_END; i++) { tmp &= ~(EXYNOS4_CLKDIV_DMC0_ACP_MASK | EXYNOS4_CLKDIV_DMC0_ACPPCLK_MASK | EXYNOS4_CLKDIV_DMC0_DPHY_MASK | EXYNOS4_CLKDIV_DMC0_DMC_MASK | EXYNOS4_CLKDIV_DMC0_DMCD_MASK | EXYNOS4_CLKDIV_DMC0_DMCP_MASK | EXYNOS4_CLKDIV_DMC0_COPY2_MASK | EXYNOS4_CLKDIV_DMC0_CORETI_MASK); tmp |= ((clkdiv_dmc0[i][0] << EXYNOS4_CLKDIV_DMC0_ACP_SHIFT) | (clkdiv_dmc0[i][1] << EXYNOS4_CLKDIV_DMC0_ACPPCLK_SHIFT) | (clkdiv_dmc0[i][2] << EXYNOS4_CLKDIV_DMC0_DPHY_SHIFT) | (clkdiv_dmc0[i][3] << EXYNOS4_CLKDIV_DMC0_DMC_SHIFT) | (clkdiv_dmc0[i][4] << EXYNOS4_CLKDIV_DMC0_DMCD_SHIFT) | (clkdiv_dmc0[i][5] << EXYNOS4_CLKDIV_DMC0_DMCP_SHIFT) | (clkdiv_dmc0[i][6] << EXYNOS4_CLKDIV_DMC0_COPY2_SHIFT) | (clkdiv_dmc0[i][7] << EXYNOS4_CLKDIV_DMC0_CORETI_SHIFT)); exynos4_busfreq_table[i].clk_dmc0div = tmp; } tmp = __raw_readl(EXYNOS4_CLKDIV_TOP); for (i = 0; i < LV_END; i++) { tmp &= ~(EXYNOS4_CLKDIV_TOP_ACLK200_MASK | EXYNOS4_CLKDIV_TOP_ACLK100_MASK | EXYNOS4_CLKDIV_TOP_ACLK160_MASK | EXYNOS4_CLKDIV_TOP_ACLK133_MASK | EXYNOS4_CLKDIV_TOP_ONENAND_MASK); tmp |= ((clkdiv_top[i][0] << EXYNOS4_CLKDIV_TOP_ACLK200_SHIFT) | (clkdiv_top[i][1] << EXYNOS4_CLKDIV_TOP_ACLK100_SHIFT) | (clkdiv_top[i][2] << EXYNOS4_CLKDIV_TOP_ACLK160_SHIFT) | (clkdiv_top[i][3] << EXYNOS4_CLKDIV_TOP_ACLK133_SHIFT) | (clkdiv_top[i][4] << EXYNOS4_CLKDIV_TOP_ONENAND_SHIFT)); exynos4_busfreq_table[i].clk_topdiv = tmp; } exynos4210_set_bus_volt(); for (i = 0; i < LV_END; i++) { ret = opp_add(dev, exynos4_busfreq_table[i].mem_clk, exynos4_busfreq_table[i].volt); if (ret) { dev_err(dev, "Fail to add opp entries.\n"); return ret; } } data->table = exynos4_busfreq_table; data->table_size = LV_END; /* Find max frequency */ data->max_opp = opp_find_freq_floor(dev, &maxfreq); data->vdd_int = regulator_get(NULL, "vdd_int"); if (IS_ERR(data->vdd_int)) { pr_err("failed to get resource %s\n", "vdd_int"); return -ENODEV; } data->vdd_mif = ERR_PTR(-ENODEV); return 0; }