| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2018 Stefan Roese <sr@denx.de> |
| * |
| * This code is mostly based on the code extracted from this MediaTek |
| * github repository: |
| * |
| * https://github.com/MediaTek-Labs/linkit-smart-uboot.git |
| * |
| * I was not able to find a specific license or other developers |
| * copyrights here, so I can't add them here. |
| * |
| * Most functions in this file are copied from the MediaTek U-Boot |
| * repository. Without any documentation, it was impossible to really |
| * implement this differently. So its mostly a cleaned-up version of |
| * the original code, with only support for the MT7628 / MT7688 SoC. |
| */ |
| |
| #include <common.h> |
| #include <linux/io.h> |
| #include <asm/cacheops.h> |
| #include <asm/io.h> |
| #include "mt76xx.h" |
| |
| #define NUM_OF_CACHELINE 128 |
| #define MIN_START 6 |
| #define MIN_FINE_START 0xf |
| #define MAX_START 7 |
| #define MAX_FINE_START 0x0 |
| |
| #define CPU_FRAC_DIV 1 |
| |
| #if defined(CONFIG_ONBOARD_DDR2_SIZE_256MBIT) |
| #define DRAM_BUTTOM 0x02000000 |
| #endif |
| #if defined(CONFIG_ONBOARD_DDR2_SIZE_512MBIT) |
| #define DRAM_BUTTOM 0x04000000 |
| #endif |
| #if defined(CONFIG_ONBOARD_DDR2_SIZE_1024MBIT) |
| #define DRAM_BUTTOM 0x08000000 |
| #endif |
| #if defined(CONFIG_ONBOARD_DDR2_SIZE_2048MBIT) |
| #define DRAM_BUTTOM 0x10000000 |
| #endif |
| |
| static inline void cal_memcpy(void *src, void *dst, u32 size) |
| { |
| u8 *psrc = (u8 *)src; |
| u8 *pdst = (u8 *)dst; |
| int i; |
| |
| for (i = 0; i < size; i++, psrc++, pdst++) |
| *pdst = *psrc; |
| } |
| |
| static inline void cal_memset(void *src, u8 pat, u32 size) |
| { |
| u8 *psrc = (u8 *)src; |
| int i; |
| |
| for (i = 0; i < size; i++, psrc++) |
| *psrc = pat; |
| } |
| |
| #define pref_op(hint, addr) \ |
| __asm__ __volatile__( \ |
| ".set push\n" \ |
| ".set noreorder\n" \ |
| "pref %0, %1\n" \ |
| ".set pop\n" \ |
| : \ |
| : "i" (hint), "R" (*(u8 *)(addr))) |
| |
| static inline void cal_patgen(u32 start_addr, u32 size, u32 bias) |
| { |
| u32 *addr = (u32 *)start_addr; |
| int i; |
| |
| for (i = 0; i < size; i++) |
| addr[i] = start_addr + i + bias; |
| } |
| |
| static inline int test_loop(int k, int dqs, u32 test_dqs, u32 *coarse_dqs, |
| u32 offs, u32 pat, u32 val) |
| { |
| u32 nc_addr; |
| u32 *c_addr; |
| int i; |
| |
| for (nc_addr = 0xa0000000; |
| nc_addr < (0xa0000000 + DRAM_BUTTOM - NUM_OF_CACHELINE * 32); |
| nc_addr += (DRAM_BUTTOM >> 6) + offs) { |
| writel(0x00007474, (void *)MT76XX_MEMCTRL_BASE + 0x64); |
| wmb(); /* Make sure store if finished */ |
| |
| c_addr = (u32 *)(nc_addr & 0xdfffffff); |
| cal_memset(((u8 *)c_addr), 0x1F, NUM_OF_CACHELINE * 32); |
| cal_patgen(nc_addr, NUM_OF_CACHELINE * 8, pat); |
| |
| if (dqs > 0) |
| writel(0x00000074 | |
| (((k == 1) ? coarse_dqs[dqs] : test_dqs) << 12) | |
| (((k == 0) ? val : test_dqs) << 8), |
| (void *)MT76XX_MEMCTRL_BASE + 0x64); |
| else |
| writel(0x00007400 | |
| (((k == 1) ? coarse_dqs[dqs] : test_dqs) << 4) | |
| (((k == 0) ? val : test_dqs) << 0), |
| (void *)MT76XX_MEMCTRL_BASE + 0x64); |
| wmb(); /* Make sure store if finished */ |
| |
| invalidate_dcache_range((u32)c_addr, |
| (u32)c_addr + |
| NUM_OF_CACHELINE * 32); |
| wmb(); /* Make sure store if finished */ |
| |
| for (i = 0; i < NUM_OF_CACHELINE * 8; i++) { |
| if (i % 8 == 0) |
| pref_op(0, &c_addr[i]); |
| } |
| |
| for (i = 0; i < NUM_OF_CACHELINE * 8; i++) { |
| if (c_addr[i] != nc_addr + i + pat) |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| void ddr_calibrate(void) |
| { |
| u32 min_coarse_dqs[2]; |
| u32 max_coarse_dqs[2]; |
| u32 min_fine_dqs[2]; |
| u32 max_fine_dqs[2]; |
| u32 coarse_dqs[2]; |
| u32 fine_dqs[2]; |
| int reg = 0, ddr_cfg2_reg; |
| int flag; |
| int i, k; |
| int dqs = 0; |
| u32 min_coarse_dqs_bnd, min_fine_dqs_bnd, coarse_dqs_dll, fine_dqs_dll; |
| u32 val; |
| u32 fdiv = 0, frac = 0; |
| |
| /* Setup clock to run at full speed */ |
| val = readl((void *)MT76XX_DYN_CFG0_REG); |
| fdiv = (u32)((val >> 8) & 0x0F); |
| if (CPU_FRAC_DIV < 1 || CPU_FRAC_DIV > 10) |
| frac = val & 0x0f; |
| else |
| frac = CPU_FRAC_DIV; |
| |
| while (frac < fdiv) { |
| val = readl((void *)MT76XX_DYN_CFG0_REG); |
| fdiv = (val >> 8) & 0x0f; |
| fdiv--; |
| val &= ~(0x0f << 8); |
| val |= (fdiv << 8); |
| writel(val, (void *)MT76XX_DYN_CFG0_REG); |
| udelay(500); |
| val = readl((void *)MT76XX_DYN_CFG0_REG); |
| fdiv = (val >> 8) & 0x0f; |
| } |
| |
| clrbits_le32((void *)MT76XX_MEMCTRL_BASE + 0x10, BIT(4)); |
| ddr_cfg2_reg = readl((void *)MT76XX_MEMCTRL_BASE + 0x48); |
| clrbits_le32((void *)MT76XX_MEMCTRL_BASE + 0x48, |
| (0x3 << 28) | (0x3 << 26)); |
| |
| min_coarse_dqs[0] = MIN_START; |
| min_coarse_dqs[1] = MIN_START; |
| min_fine_dqs[0] = MIN_FINE_START; |
| min_fine_dqs[1] = MIN_FINE_START; |
| max_coarse_dqs[0] = MAX_START; |
| max_coarse_dqs[1] = MAX_START; |
| max_fine_dqs[0] = MAX_FINE_START; |
| max_fine_dqs[1] = MAX_FINE_START; |
| dqs = 0; |
| |
| /* Add by KP, DQS MIN boundary */ |
| reg = readl((void *)MT76XX_MEMCTRL_BASE + 0x20); |
| coarse_dqs_dll = (reg & 0xf00) >> 8; |
| fine_dqs_dll = (reg & 0xf0) >> 4; |
| if (coarse_dqs_dll <= 8) |
| min_coarse_dqs_bnd = 8 - coarse_dqs_dll; |
| else |
| min_coarse_dqs_bnd = 0; |
| |
| if (fine_dqs_dll <= 8) |
| min_fine_dqs_bnd = 8 - fine_dqs_dll; |
| else |
| min_fine_dqs_bnd = 0; |
| /* DQS MIN boundary */ |
| |
| DQS_CAL: |
| |
| for (k = 0; k < 2; k++) { |
| u32 test_dqs; |
| |
| if (k == 0) |
| test_dqs = MAX_START; |
| else |
| test_dqs = MAX_FINE_START; |
| |
| do { |
| flag = test_loop(k, dqs, test_dqs, max_coarse_dqs, |
| 0x400, 0x3, 0xf); |
| if (flag == -1) |
| break; |
| |
| test_dqs++; |
| } while (test_dqs <= 0xf); |
| |
| if (k == 0) { |
| max_coarse_dqs[dqs] = test_dqs; |
| } else { |
| test_dqs--; |
| |
| if (test_dqs == MAX_FINE_START - 1) { |
| max_coarse_dqs[dqs]--; |
| max_fine_dqs[dqs] = 0xf; |
| } else { |
| max_fine_dqs[dqs] = test_dqs; |
| } |
| } |
| } |
| |
| for (k = 0; k < 2; k++) { |
| u32 test_dqs; |
| |
| if (k == 0) |
| test_dqs = MIN_START; |
| else |
| test_dqs = MIN_FINE_START; |
| |
| do { |
| flag = test_loop(k, dqs, test_dqs, min_coarse_dqs, |
| 0x480, 0x1, 0x0); |
| if (k == 0) { |
| if (flag == -1 || |
| test_dqs == min_coarse_dqs_bnd) |
| break; |
| |
| test_dqs--; |
| |
| if (test_dqs < min_coarse_dqs_bnd) |
| break; |
| } else { |
| if (flag == -1) { |
| test_dqs++; |
| break; |
| } else if (test_dqs == min_fine_dqs_bnd) { |
| break; |
| } |
| |
| test_dqs--; |
| |
| if (test_dqs < min_fine_dqs_bnd) |
| break; |
| } |
| } while (test_dqs >= 0); |
| |
| if (k == 0) { |
| min_coarse_dqs[dqs] = test_dqs; |
| } else { |
| if (test_dqs == MIN_FINE_START + 1) { |
| min_coarse_dqs[dqs]++; |
| min_fine_dqs[dqs] = 0x0; |
| } else { |
| min_fine_dqs[dqs] = test_dqs; |
| } |
| } |
| } |
| |
| if (dqs == 0) { |
| dqs = 1; |
| goto DQS_CAL; |
| } |
| |
| for (i = 0; i < 2; i++) { |
| u32 temp; |
| |
| coarse_dqs[i] = (max_coarse_dqs[i] + min_coarse_dqs[i]) >> 1; |
| temp = |
| (((max_coarse_dqs[i] + min_coarse_dqs[i]) % 2) * 4) + |
| ((max_fine_dqs[i] + min_fine_dqs[i]) >> 1); |
| if (temp >= 0x10) { |
| coarse_dqs[i]++; |
| fine_dqs[i] = (temp - 0x10) + 0x8; |
| } else { |
| fine_dqs[i] = temp; |
| } |
| } |
| reg = (coarse_dqs[1] << 12) | (fine_dqs[1] << 8) | |
| (coarse_dqs[0] << 4) | fine_dqs[0]; |
| |
| clrbits_le32((void *)MT76XX_MEMCTRL_BASE + 0x10, BIT(4)); |
| writel(reg, (void *)MT76XX_MEMCTRL_BASE + 0x64); |
| writel(ddr_cfg2_reg, (void *)MT76XX_MEMCTRL_BASE + 0x48); |
| setbits_le32((void *)MT76XX_MEMCTRL_BASE + 0x10, BIT(4)); |
| |
| for (i = 0; i < 2; i++) |
| debug("[%02X%02X%02X%02X]", min_coarse_dqs[i], |
| min_fine_dqs[i], max_coarse_dqs[i], max_fine_dqs[i]); |
| debug("\nDDR Calibration DQS reg = %08X\n", reg); |
| } |