diff options
Diffstat (limited to 'drivers/ddr/imx/ddrphy_utils.c')
-rw-r--r-- | drivers/ddr/imx/ddrphy_utils.c | 533 |
1 files changed, 533 insertions, 0 deletions
diff --git a/drivers/ddr/imx/ddrphy_utils.c b/drivers/ddr/imx/ddrphy_utils.c new file mode 100644 index 0000000000..353a265136 --- /dev/null +++ b/drivers/ddr/imx/ddrphy_utils.c @@ -0,0 +1,533 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* +* Copyright 2018 NXP +*/ + +#define pr_fmt(fmt) "imx8m-ddr: " fmt + +#include <common.h> +#include <errno.h> +#include <io.h> +#include <linux/iopoll.h> +#include <soc/imx8m/ddr.h> +#include <mach/imx/imx8m-regs.h> +#include <mach/imx/imx8m-ccm-regs.h> + +/* DDR Transfer rate, bus clock is transfer rate / 2, and the DDRC runs at bus + * clock / 2, which is therefor transfer rate / 4. */ +enum ddr_rate { + DDR_4000, + DDR_3720, + DDR_3200, + DDR_3000, + DDR_2600, /* Unused */ + DDR_2400, + DDR_2376, /* Unused */ + DDR_1600, + DDR_1000, /* Unused */ + DDR_1066, + DDR_667, + DDR_400, + DDR_250, /* Unused */ + DDR_100, + DDR_NUM_RATES +}; + +/* PLL config for IMX8MM type DRAM PLL. This PLL type isn't documented, but + * it looks like it is a basically a fractional PLL: + * Frequency = Ref (24 MHz) / P * M / 2^S + * Note: Divider is equal to register value + */ +#define MDIV(x) ((x) << 12) +#define PDIV(x) ((x) << 4) +#define SDIV(x) ((x) << 0) + +#define LOCK_STATUS BIT(31) +#define LOCK_SEL_MASK BIT(29) +#define CLKE_MASK BIT(11) +#define RST_MASK BIT(9) +#define BYPASS_MASK BIT(4) + +static const struct imx8mm_fracpll_config { + uint32_t r1, r2; + bool valid; +} imx8mm_fracpll_table[DDR_NUM_RATES] = { + [DDR_4000] = { .valid = true, .r1 = MDIV(250) | PDIV(3) | SDIV(1), .r2 = 0 }, + [DDR_3720] = { .valid = true, .r1 = MDIV(310) | PDIV(2) | SDIV(2), .r2 = 0 }, + [DDR_3200] = { .valid = true, .r1 = MDIV(300) | PDIV(9) | SDIV(0), .r2 = 0 }, + [DDR_3000] = { .valid = true, .r1 = MDIV(250) | PDIV(8) | SDIV(0), .r2 = 0 }, + [DDR_2600] = { .valid = true, .r1 = MDIV(325) | PDIV(3) | SDIV(2), .r2 = 0 }, + [DDR_2400] = { .valid = true, .r1 = MDIV(300) | PDIV(3) | SDIV(2), .r2 = 0 }, + [DDR_2376] = { .valid = true, .r1 = MDIV( 99) | PDIV(1) | SDIV(2), .r2 = 0 }, + [DDR_1600] = { .valid = true, .r1 = MDIV(300) | PDIV(9) | SDIV(1), .r2 = 0 }, + [DDR_1066] = { .valid = true, .r1 = MDIV(400) | PDIV(9) | SDIV(2), .r2 = 0 }, + [DDR_667] = { .valid = true, .r1 = MDIV(334) | PDIV(3) | SDIV(4), .r2 = 0 }, + [DDR_400] = { .valid = true, .r1 = MDIV(300) | PDIV(9) | SDIV(3), .r2 = 0 }, +}; + +/* PLL config for IMX8MQ type DRAM PLL. This is SSCG_PLL: + * Frequency = Ref (25 MHz) / divr1 * (2*divf1) / divr2 * divf2 / divq + * Note: IMX8MQ RM, §5.1.5.4.4 Fig. 5-8 shows ÷2 on divf2, but this is not true. + * Note: divider is register value + 1 + */ +#define SSCG_PLL_LOCK BIT(31) +#define SSCG_PLL_DRAM_PLL_CLKE BIT(9) +#define SSCG_PLL_PD BIT(7) +#define SSCG_PLL_BYPASS1 BIT(5) +#define SSCG_PLL_BYPASS2 BIT(4) + +#define SSCG_PLL_REF_DIVR2_MASK (0x3f << 19) +#define SSCG_PLL_REF_DIVR2_VAL(n) (((n) << 19) & SSCG_PLL_REF_DIVR2_MASK) +#define SSCG_PLL_FEEDBACK_DIV_F1_MASK (0x3f << 13) +#define SSCG_PLL_FEEDBACK_DIV_F1_VAL(n) (((n) << 13) & SSCG_PLL_FEEDBACK_DIV_F1_MASK) +#define SSCG_PLL_FEEDBACK_DIV_F2_MASK (0x3f << 7) +#define SSCG_PLL_FEEDBACK_DIV_F2_VAL(n) (((n) << 7) & SSCG_PLL_FEEDBACK_DIV_F2_MASK) +#define SSCG_PLL_OUTPUT_DIV_VAL_MASK (0x3f << 1) +#define SSCG_PLL_OUTPUT_DIV_VAL(n) (((n) << 1) & SSCG_PLL_OUTPUT_DIV_VAL_MASK) + +#define SSCG_PLL_CFG2(divf1, divr2, divf2, divq) \ + (SSCG_PLL_FEEDBACK_DIV_F1_VAL(divf1) | SSCG_PLL_FEEDBACK_DIV_F2_VAL(divf2) | \ + SSCG_PLL_REF_DIVR2_VAL(divr2) | SSCG_PLL_OUTPUT_DIV_VAL(divq)) + +static const struct imx8mq_ssgcpll_config { + uint32_t val; + bool valid; +} imx8mq_ssgcpll_table[DDR_NUM_RATES] = { + [DDR_3200] = { .valid = true, .val = SSCG_PLL_CFG2(39, 29, 11, 0) }, + [DDR_2400] = { .valid = true, .val = SSCG_PLL_CFG2(39, 29, 17, 1) }, + [DDR_1600] = { .valid = true, .val = SSCG_PLL_CFG2(39, 29, 11, 1) }, + [DDR_667] = { .valid = true, .val = SSCG_PLL_CFG2(45, 30, 8, 3) }, /* ~166.935 MHz = 667.74 */ +}; + +/* IMX8M Bypass clock config. These configure dram_alt1_clk and the dram apb + * clock. For the bypass config, clock rate = DRAM tranfer rate, rather than + * clock = dram / 4 + */ + +/* prediv is actual divider, register will be set to divider - 1 */ +#define CCM_ROOT_CFG(mux, prediv) (IMX8M_CCM_TARGET_ROOTn_ENABLE | \ + IMX8M_CCM_TARGET_ROOTn_MUX(mux) | IMX8M_CCM_TARGET_ROOTn_PRE_DIV(prediv-1)) + +static const struct imx8m_bypass_config { + uint32_t alt_clk; + uint32_t apb_clk; + bool valid; +} imx8m_bypass_table[DDR_NUM_RATES] = { + [DDR_400] = { .valid = true, .alt_clk = CCM_ROOT_CFG(1, 2), .apb_clk = CCM_ROOT_CFG(3, 2) }, + [DDR_250] = { .valid = true, .alt_clk = CCM_ROOT_CFG(3, 2), .apb_clk = CCM_ROOT_CFG(2, 2) }, + [DDR_100] = { .valid = true, .alt_clk = CCM_ROOT_CFG(2, 1), .apb_clk = CCM_ROOT_CFG(2, 2) }, +}; + +void ddrc_phy_load_firmware(void __iomem *phy, + enum ddrc_phy_firmware_offset offset, + const u16 *blob, size_t size) +{ + while (size) { + writew(*blob++, phy + DDRC_PHY_REG(offset)); + offset++; + size -= sizeof(*blob); + } +} + +enum pmc_constants { + PMC_MESSAGE_ID, + PMC_MESSAGE_STREAM, + + PMC_TRAIN_SUCCESS = 0x07, + PMC_TRAIN_STREAM_START = 0x08, + PMC_TRAIN_FAIL = 0xff, +}; + +static u32 ddrc_phy_get_message(void __iomem *phy, int type) +{ + u32 r, message; + + /* + * When BIT0 set to 0, the PMU has a message for the user + * Wait for it indefinitely. + */ + readl_poll_timeout(phy + DDRC_PHY_REG(0xd0004), + r, !(r & BIT(0)), 0); + + switch (type) { + case PMC_MESSAGE_ID: + /* + * Get the major message ID + */ + message = readl(phy + DDRC_PHY_REG(0xd0032)); + break; + case PMC_MESSAGE_STREAM: + message = readl(phy + DDRC_PHY_REG(0xd0034)); + message <<= 16; + message |= readl(phy + DDRC_PHY_REG(0xd0032)); + break; + } + + /* + * By setting this register to 0, the user acknowledges the + * receipt of the message. + */ + writel(0x00000000, phy + DDRC_PHY_REG(0xd0031)); + /* + * When BIT0 set to 0, the PMU has a message for the user + */ + readl_poll_timeout(phy + DDRC_PHY_REG(0xd0004), + r, r & BIT(0), 0); + + writel(0x00000001, phy + DDRC_PHY_REG(0xd0031)); + + return message; +} + +static void ddrc_phy_fetch_streaming_message(void __iomem *phy) +{ + const u16 index = ddrc_phy_get_message(phy, PMC_MESSAGE_STREAM); + u16 i; + + for (i = 0; i < index; i++) + ddrc_phy_get_message(phy, PMC_MESSAGE_STREAM); +} + +int wait_ddrphy_training_complete(void) +{ + void __iomem *phy = IOMEM(MX8M_DDRC_PHY_BASE_ADDR); + + for (;;) { + const u32 m = ddrc_phy_get_message(phy, PMC_MESSAGE_ID); + + switch (m) { + case PMC_TRAIN_STREAM_START: + ddrc_phy_fetch_streaming_message(phy); + break; + case PMC_TRAIN_SUCCESS: + return 0; + case PMC_TRAIN_FAIL: + hang(); + } + } +} + +static void dram_enable_bypass(enum ddr_rate drate) +{ + const struct imx8m_bypass_config *config = &imx8m_bypass_table[drate]; + + if (!config->valid) { + pr_warn("No matched freq table entry %u\n", drate); + return; + } + + imx8m_clock_set_target_val(IMX8M_DRAM_ALT_CLK_ROOT, config->alt_clk); + imx8m_clock_set_target_val(IMX8M_DRAM_APB_CLK_ROOT, config->apb_clk); + imx8m_clock_set_target_val(IMX8M_DRAM_SEL_CFG, IMX8M_CCM_TARGET_ROOTn_ENABLE | + IMX8M_CCM_TARGET_ROOTn_MUX(1)); +} + +static void dram_disable_bypass(void) +{ + imx8m_clock_set_target_val(IMX8M_DRAM_SEL_CFG, + IMX8M_CCM_TARGET_ROOTn_ENABLE | + IMX8M_CCM_TARGET_ROOTn_MUX(0)); + imx8m_clock_set_target_val(IMX8M_DRAM_APB_CLK_ROOT, + IMX8M_CCM_TARGET_ROOTn_ENABLE | + IMX8M_CCM_TARGET_ROOTn_MUX(4) | + IMX8M_CCM_TARGET_ROOTn_PRE_DIV(5 - 1)); +} + +static int dram_frac_pll_init(enum ddr_rate drate) +{ + volatile int i; + u32 tmp; + void *pll_base; + const struct imx8mm_fracpll_config *config = &imx8mm_fracpll_table[drate]; + + if (!config->valid) { + pr_warn("No matched freq table entry %u\n", drate); + return -EINVAL; + } + + setbits_le32(MX8M_GPC_BASE_ADDR + 0xec, 1 << 7); + setbits_le32(MX8M_GPC_BASE_ADDR + 0xf8, 1 << 5); + writel(0x8F000000UL, MX8M_SRC_BASE_ADDR + 0x1004); + + pll_base = IOMEM(MX8M_ANATOP_BASE_ADDR) + 0x50; + + /* Bypass clock and set lock to pll output lock */ + tmp = readl(pll_base); + tmp |= BYPASS_MASK; + writel(tmp, pll_base); + + /* Enable RST */ + tmp &= ~RST_MASK; + writel(tmp, pll_base); + + writel(config->r1, pll_base + 4); + writel(config->r2, pll_base + 8); + + for (i = 0; i < 1000; i++); + + /* Disable RST */ + tmp |= RST_MASK; + writel(tmp, pll_base); + + /* Wait Lock*/ + while (!(readl(pll_base) & LOCK_STATUS)); + + /* Bypass */ + tmp &= ~BYPASS_MASK; + writel(tmp, pll_base); + + return 0; +} + +static int dram_sscg_pll_init(enum ddr_rate drate) +{ + u32 val; + void __iomem *pll_base = IOMEM(MX8M_ANATOP_BASE_ADDR) + 0x60; + const struct imx8mq_ssgcpll_config *config = &imx8mq_ssgcpll_table[drate]; + + if (!config->valid) { + pr_warn("No matched freq table entry %u\n", drate); + return -EINVAL; + } + + /* Bypass */ + setbits_le32(pll_base, SSCG_PLL_BYPASS1 | SSCG_PLL_BYPASS2); + + val = readl(pll_base + 0x8); + val &= ~(SSCG_PLL_OUTPUT_DIV_VAL_MASK | + SSCG_PLL_FEEDBACK_DIV_F2_MASK | + SSCG_PLL_FEEDBACK_DIV_F1_MASK | + SSCG_PLL_REF_DIVR2_MASK); + val |= config->val; + writel(val, pll_base + 0x8); + + /* Clear power down bit */ + clrbits_le32(pll_base, SSCG_PLL_PD); + /* Enable PLL */ + setbits_le32(pll_base, SSCG_PLL_DRAM_PLL_CLKE); + + /* Clear bypass */ + clrbits_le32(pll_base, SSCG_PLL_BYPASS1); + udelay(100); + clrbits_le32(pll_base, SSCG_PLL_BYPASS2); + /* Wait lock */ + while (!(readl(pll_base) & SSCG_PLL_LOCK)) + ; + + return 0; +} + +static int dram_pll_init(enum ddr_rate drate, enum ddrc_type type) +{ + switch (type) { + case DDRC_TYPE_MQ: + return dram_sscg_pll_init(drate); + case DDRC_TYPE_MM: + case DDRC_TYPE_MN: + case DDRC_TYPE_MP: + return dram_frac_pll_init(drate); + default: + return -ENODEV; + } +} + +void ddrphy_init_set_dfi_clk(unsigned int drate_mhz, enum ddrc_type type) +{ + enum ddr_rate drate; + + switch (drate_mhz) { + case 4000: drate = DDR_4000; break; + case 3720: drate = DDR_3720; break; + case 3200: drate = DDR_3200; break; + case 3000: drate = DDR_3000; break; + case 2400: drate = DDR_2400; break; + case 1600: drate = DDR_1600; break; + case 1066: drate = DDR_1066; break; + case 667: drate = DDR_667; break; + case 400: drate = DDR_400; break; + case 100: drate = DDR_100; break; + default: + pr_warn("Unsupported frequency %u\n", drate_mhz); + return; + } + + if (drate_mhz > 400) { + dram_pll_init(drate, type); + dram_disable_bypass(); + } else { + dram_enable_bypass(drate); + } +} + +void ddrphy_init_read_msg_block(enum fw_type type) +{ +} + +static unsigned int g_cdd_rr_max[4]; +static unsigned int g_cdd_rw_max[4]; +static unsigned int g_cdd_wr_max[4]; +static unsigned int g_cdd_ww_max[4]; + +static unsigned int look_for_max(unsigned int data[], unsigned int addr_start, + unsigned int addr_end) +{ + unsigned int i, imax = 0; + + for (i = addr_start; i <= addr_end; i++) { + if (((data[i] >> 7) == 0) && (data[i] > imax)) + imax = data[i]; + } + + return imax; +} + +void get_trained_CDD(u32 fsp) +{ + unsigned int i, ddr_type, tmp; + unsigned int cdd_cha[12], cdd_chb[12]; + unsigned int cdd_cha_rr_max, cdd_cha_rw_max, cdd_cha_wr_max, cdd_cha_ww_max; + unsigned int cdd_chb_rr_max, cdd_chb_rw_max, cdd_chb_wr_max, cdd_chb_ww_max; + + ddr_type = reg32_read(DDRC_MSTR(0)) & 0x3f; + if (ddr_type == 0x20) { + for (i = 0; i < 6; i++) { + tmp = reg32_read(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + + (0x54013UL + i) * 4); + cdd_cha[i * 2] = tmp & 0xff; + cdd_cha[i * 2 + 1] = (tmp >> 8) & 0xff; + } + + for (i = 0; i < 7; i++) { + tmp = reg32_read(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + + (0x5402cUL + i) * 4); + if (i == 0) { + cdd_cha[0] = (tmp >> 8) & 0xff; + } else if (i == 6) { + cdd_cha[11] = tmp & 0xff; + } else { + cdd_chb[ i * 2 - 1] = tmp & 0xff; + cdd_chb[i * 2] = (tmp >> 8) & 0xff; + } + } + + cdd_cha_rr_max = look_for_max(cdd_cha, 0, 1); + cdd_cha_rw_max = look_for_max(cdd_cha, 2, 5); + cdd_cha_wr_max = look_for_max(cdd_cha, 6, 9); + cdd_cha_ww_max = look_for_max(cdd_cha, 10, 11); + cdd_chb_rr_max = look_for_max(cdd_chb, 0, 1); + cdd_chb_rw_max = look_for_max(cdd_chb, 2, 5); + cdd_chb_wr_max = look_for_max(cdd_chb, 6, 9); + cdd_chb_ww_max = look_for_max(cdd_chb, 10, 11); + g_cdd_rr_max[fsp] = cdd_cha_rr_max > cdd_chb_rr_max ? cdd_cha_rr_max : cdd_chb_rr_max; + g_cdd_rw_max[fsp] = cdd_cha_rw_max > cdd_chb_rw_max ? cdd_cha_rw_max : cdd_chb_rw_max; + g_cdd_wr_max[fsp] = cdd_cha_wr_max > cdd_chb_wr_max ? cdd_cha_wr_max : cdd_chb_wr_max; + g_cdd_ww_max[fsp] = cdd_cha_ww_max > cdd_chb_ww_max ? cdd_cha_ww_max : cdd_chb_ww_max; + } else { + unsigned int ddr4_cdd[64]; + + for( i = 0; i < 29; i++) { + tmp = reg32_read(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + + (0x54012UL + i) * 4); + ddr4_cdd[i * 2] = tmp & 0xff; + ddr4_cdd[i * 2 + 1] = (tmp >> 8) & 0xff; + } + + g_cdd_rr_max[fsp] = look_for_max(ddr4_cdd, 1, 12); + g_cdd_ww_max[fsp] = look_for_max(ddr4_cdd, 13, 24); + g_cdd_rw_max[fsp] = look_for_max(ddr4_cdd, 25, 40); + g_cdd_wr_max[fsp] = look_for_max(ddr4_cdd, 41, 56); + } +} + +void update_umctl2_rank_space_setting(unsigned int pstat_num, + enum ddrc_type type) +{ + unsigned int i,ddr_type; + unsigned int rdata, tmp, tmp_t; + unsigned int ddrc_w2r,ddrc_r2w,ddrc_wr_gap,ddrc_rd_gap; + unsigned long addr_slot; + + ddr_type = reg32_read(DDRC_MSTR(0)) & 0x3f; + for (i = 0; i < pstat_num; i++) { + addr_slot = i ? (i + 1) * 0x1000 : 0; + if (ddr_type == 0x20) { + /* update r2w:[13:8], w2r:[5:0] */ + rdata = reg32_read(DDRC_DRAMTMG2(0) + addr_slot); + ddrc_w2r = rdata & 0x3f; + if (type == DDRC_TYPE_MP) + tmp = ddrc_w2r + (g_cdd_wr_max[i] >> 1); + else + tmp = ddrc_w2r + (g_cdd_wr_max[i] >> 1) + 1; + ddrc_w2r = (tmp > 0x3f) ? 0x3f : tmp; + + ddrc_r2w = (rdata >> 8) & 0x3f; + if (type == DDRC_TYPE_MP) + tmp = ddrc_r2w + (g_cdd_rw_max[i] >> 1); + else + tmp = ddrc_r2w + (g_cdd_rw_max[i] >> 1) + 1; + ddrc_r2w = (tmp > 0x3f) ? 0x3f : tmp; + + tmp_t = (rdata & 0xffffc0c0) | (ddrc_r2w << 8) | ddrc_w2r; + reg32_write((DDRC_DRAMTMG2(0) + addr_slot), tmp_t); + } else { + /* update w2r:[5:0] */ + rdata = reg32_read(DDRC_DRAMTMG9(0) + addr_slot); + ddrc_w2r = rdata & 0x3f; + if (type == DDRC_TYPE_MP) + tmp = ddrc_w2r + (g_cdd_wr_max[i] >> 1); + else + tmp = ddrc_w2r + (g_cdd_wr_max[i] >> 1) + 1; + ddrc_w2r = (tmp > 0x3f) ? 0x3f : tmp; + tmp_t = (rdata & 0xffffffc0) | ddrc_w2r; + reg32_write((DDRC_DRAMTMG9(0) + addr_slot), tmp_t); + + /* update r2w:[13:8] */ + rdata = reg32_read(DDRC_DRAMTMG2(0) + addr_slot); + ddrc_r2w = (rdata >> 8) & 0x3f; + if (type == DDRC_TYPE_MP) + tmp = ddrc_r2w + (g_cdd_rw_max[i] >> 1); + else + tmp = ddrc_r2w + (g_cdd_rw_max[i] >> 1) + 1; + ddrc_r2w = (tmp > 0x3f) ? 0x3f : tmp; + + tmp_t = (rdata & 0xffffc0ff) | (ddrc_r2w << 8); + reg32_write((DDRC_DRAMTMG2(0) + addr_slot), tmp_t); + } + + if (type != DDRC_TYPE_MQ) { + /* update rankctl: wr_gap:11:8; rd:gap:7:4; quasi-dymic, doc wrong(static) */ + rdata = reg32_read(DDRC_RANKCTL(0) + addr_slot); + ddrc_wr_gap = (rdata >> 8) & 0xf; + if (type == DDRC_TYPE_MP) + tmp = ddrc_wr_gap + (g_cdd_ww_max[i] >> 1); + else + tmp = ddrc_wr_gap + (g_cdd_ww_max[i] >> 1) + 1; + ddrc_wr_gap = (tmp > 0xf) ? 0xf : tmp; + + ddrc_rd_gap = (rdata >> 4) & 0xf; + if (type == DDRC_TYPE_MP) + tmp = ddrc_rd_gap + (g_cdd_rr_max[i] >> 1); + else + tmp = ddrc_rd_gap + (g_cdd_rr_max[i] >> 1) + 1; + ddrc_rd_gap = (tmp > 0xf) ? 0xf : tmp; + + tmp_t = (rdata & 0xfffff00f) | (ddrc_wr_gap << 8) | (ddrc_rd_gap << 4); + reg32_write((DDRC_RANKCTL(0) + addr_slot), tmp_t); + } + } + + if (type == DDRC_TYPE_MQ) { + /* update rankctl: wr_gap:11:8; rd:gap:7:4; quasi-dymic, doc wrong(static) */ + rdata = reg32_read(DDRC_RANKCTL(0)); + ddrc_wr_gap = (rdata >> 8) & 0xf; + tmp = ddrc_wr_gap + (g_cdd_ww_max[0] >> 1) + 1; + ddrc_wr_gap = (tmp > 0xf) ? 0xf : tmp; + + ddrc_rd_gap = (rdata >> 4) & 0xf; + tmp = ddrc_rd_gap + (g_cdd_rr_max[0] >> 1) + 1; + ddrc_rd_gap = (tmp > 0xf) ? 0xf : tmp; + + tmp_t = (rdata & 0xfffff00f) | (ddrc_wr_gap << 8) | (ddrc_rd_gap << 4); + reg32_write(DDRC_RANKCTL(0), tmp_t); + } +} |