/* * Copyright(c) 2018-2021 Allwinnertech Co., Ltd. * * Hardware dram frequency scaling, for which code run in dram. * * Author: frank * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. */ #define MDFS_DEBUG 0 /* helper for debug */ #if MDFS_DEBUG static void reg_dump(struct sunxi_dramfreq *dramfreq) { printk("MC_MDFSCR:%x\n", readl(dramfreq->dramcom_base + MC_MDFSCR)); printk("VTFCR:%x\n", readl(dramfreq->dramctl_base + VTFCR)); printk("RFSHTMG:%x\n", readl(dramfreq->dramctl_base + RFSHTMG)); printk("DXnGCR0(0):%x\n", readl(dramfreq->dramctl_base + DXnGCR0(0))); printk("DXnGCR0(1):%x\n", readl(dramfreq->dramctl_base + DXnGCR0(1))); printk("DXnGCR0(2):%x\n", readl(dramfreq->dramctl_base + DXnGCR0(2))); printk("DXnGCR0(3):%x\n", readl(dramfreq->dramctl_base + DXnGCR0(3))); printk("ODTMAP:%x\n", readl(dramfreq->dramctl_base + ODTMAP)); printk("_DRAM_CLK_REG:%x\n", readl(dramfreq->ccu_base + _DRAM_CLK_REG)); printk("PGCR0:%x\n", readl(dramfreq->dramctl_base + PGCR0)); } #endif /* CONFIG_ARCH_SUN8IW16 */ #if defined(CONFIG_ARCH_SUN8IW16) #include #include static int mdfs_dfs(struct sunxi_dramfreq *dramfreq, unsigned int freq) { struct arm_smccc_res res; arm_smccc_smc(ARM_SVC_ARISC_DRAM_DVFS_REQ, freq, 0, 0, 0, 0, 0, 0, &res); return res.a0; } #endif /* CONFIG_ARCH_SUN8IW18 */ #if defined(CONFIG_ARCH_SUN8IW18) static int mdfs_dfs(struct sunxi_dramfreq *dramfreq, unsigned int freq) { struct dram_para_t *para = &dramfreq->dram_para; unsigned int rank_num; uint32_t reg_val; unsigned int trefi, trfc, ctrl_freq; unsigned int i = 0, n = 4; unsigned int div, source; unsigned int vtf ; //bit0 must be 0 for new MDFS process while (readl(dramfreq->dramcom_base + MC_MDFSCR) & 0x1) ; //calculate source and divider if (para->dram_tpr9 != 0) { if (((para->dram_clk % freq) == 0) && ((para->dram_tpr9 % freq) == 0)) { if ((para->dram_clk / freq) > (para->dram_tpr9 / freq)) { source = 0; div = para->dram_tpr9 / freq; } else { source = 1; div = para->dram_clk / freq; } } else if ((para->dram_clk % freq) == 0) { source = 1; div = para->dram_clk / freq; } else if ((para->dram_tpr9 % freq) == 0) { source = 0; div = para->dram_tpr9 / freq; } else{ printk("MDFS fail!\n"); return 1; } } else { source = (para->dram_tpr13 >> 6) & 0x1; div = para->dram_clk / freq; } ctrl_freq = freq >> 1; if ((para->dram_type == 3) || (para->dram_type == 2)) { trefi = ((7800 * ctrl_freq) / 1000 + ((((7800 * ctrl_freq) % 1000) != 0) ? 1 : 0)) / 32; trfc = (350 * ctrl_freq) / 1000 + ((((350 * ctrl_freq) % 1000) != 0) ? 1 : 0); } else { trefi = ((3900 * ctrl_freq) / 1000 + ((((3900 * ctrl_freq) % 1000) != 0) ? 1 : 0)) / 32; trfc = (210 * ctrl_freq) / 1000 + ((((210 * ctrl_freq) % 1000) != 0) ? 1 : 0); } /*turn off vtf*/ vtf = (readl(dramfreq->dramctl_base + VTFCR) >> 8) & 0x3; if (vtf) { reg_val = readl(dramfreq->dramctl_base + VTFCR); reg_val &= ~(0x3 << 8); writel(reg_val, dramfreq->dramctl_base + VTFCR); } /* set dual buffer for timing change and power save */ reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR); /* VTC dual buffer can not be used */ reg_val |= (0x1U << 15); writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR); /* change refresh timing */ reg_val = readl(dramfreq->dramctl_base + RFSHTMG); reg_val &= ~((0xfff << 0) | (0xfff << 16)); reg_val |= ((trfc << 0) | (trefi << 16)); writel(reg_val, dramfreq->dramctl_base + RFSHTMG); /* change ODT status for power save */ if (!((para->dram_tpr13 >> 12) & 0x1)) { if (freq > 400) { if ((para->dram_odt_en & 0x1)) { for (i = 0; i < n; i++) { //byte 0/byte 1 reg_val = readl(dramfreq->dramctl_base + DXnGCR0(i)); reg_val &= ~(0x3U << 4); reg_val |= (0x0 << 4); //ODT dynamic writel(reg_val, dramfreq->dramctl_base + DXnGCR0(i)); } rank_num = readl(dramfreq->dramcom_base + MC_WORK_MODE) & 0x1; if (rank_num) { writel(0x303, dramfreq->dramctl_base + ODTMAP); } else { writel(0x201, dramfreq->dramctl_base + ODTMAP); } } } else { if ((para->dram_odt_en & 0x1)) { for (i = 0; i < n; i++) { //byte 0/byte 1 reg_val = readl(dramfreq->dramctl_base + DXnGCR0(i)); reg_val &= ~(0x3U << 4); reg_val |= (0x2 << 4); //ODT off writel(reg_val, dramfreq->dramctl_base + DXnGCR0(i)); } writel(0x0, dramfreq->dramctl_base + ODTMAP); } } } //set the DRAM_CFG_REG divider in CCMU reg_val = readl(dramfreq->ccu_base + _DRAM_CLK_REG); reg_val &= ~(0xf << 0); reg_val |= ((div - 1) << 0); reg_val &= ~(0x3 << 24); // reg_val |= (source << 24); reg_val |= (0x1 << 24); //AW1821Ö»ÓÐPLL_DDR0£¬Ð´¹Ì¶¨Öµ writel(reg_val, dramfreq->ccu_base + _DRAM_CLK_REG); /* set MDFS register */ reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR); reg_val |= (0x1 << 4); //bypass // reg_val |= (0x1 << 13); //pad hold reg_val &= ~(0x1U << 1); //DFS mode writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR); reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR); reg_val |= (0x1U << 0); //start mdfs writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR); /* wait for process finished */ while (readl(dramfreq->dramcom_base + MC_MDFSCR) & 0x1) ; /* turn off dual buffer */ reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR); reg_val &= ~(0x1U << 15); // reg_val &= ~(0x1 << 13); //pad hold writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR); /*turn on vtf*/ if (vtf) { reg_val = readl(dramfreq->dramctl_base + VTFCR); reg_val |= (0x3<<8); writel(reg_val, dramfreq->dramctl_base + VTFCR); } return 0; } #endif /* CONFIG_ARCH_SUN8IW18 */