diff options
Diffstat (limited to 'drivers/ddr/imx8m/ddrphy_utils.c')
-rw-r--r-- | drivers/ddr/imx8m/ddrphy_utils.c | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/drivers/ddr/imx8m/ddrphy_utils.c b/drivers/ddr/imx8m/ddrphy_utils.c new file mode 100644 index 0000000000..651bb4b698 --- /dev/null +++ b/drivers/ddr/imx8m/ddrphy_utils.c @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* +* Copyright 2018 NXP +*/ + +#include <common.h> +#include <errno.h> +#include <io.h> +#include <linux/iopoll.h> +#include <soc/imx8m/ddr.h> +#include <mach/imx8m-regs.h> +#include <mach/imx8m-ccm-regs.h> + +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(); + } + } +} + +struct dram_bypass_clk_setting { + ulong clk; + int alt_root_sel; + int alt_pre_div; + int apb_root_sel; + int apb_pre_div; +}; + +#define MHZ(x) (1000000UL * (x)) + +static struct dram_bypass_clk_setting imx8mq_dram_bypass_tbl[] = { + { + .clk = MHZ(100), + .alt_root_sel = 2, + .alt_pre_div = 1 - 1, + .apb_root_sel = 2, + .apb_pre_div = 2 - 1, + } , { + .clk = MHZ(250), + .alt_root_sel = 3, + .alt_pre_div = 2 - 1, + .apb_root_sel = 2, + .apb_pre_div = 2 - 1, + }, { + .clk = MHZ(400), + .alt_root_sel = 1, + .alt_pre_div = 2 - 1, + .apb_root_sel = 3, + .apb_pre_div = 2 - 1, + }, +}; + +static void dram_enable_bypass(ulong clk_val) +{ + int i; + struct dram_bypass_clk_setting *config; + + for (i = 0; i < ARRAY_SIZE(imx8mq_dram_bypass_tbl); i++) { + if (clk_val == imx8mq_dram_bypass_tbl[i].clk) + break; + } + + if (i == ARRAY_SIZE(imx8mq_dram_bypass_tbl)) { + printf("No matched freq table %lu\n", clk_val); + return; + } + + config = &imx8mq_dram_bypass_tbl[i]; + + imx8m_clock_set_target_val(IMX8M_DRAM_ALT_CLK_ROOT, + IMX8M_CCM_TARGET_ROOTn_ENABLE | + IMX8M_CCM_TARGET_ROOTn_MUX(config->alt_root_sel) | + IMX8M_CCM_TARGET_ROOTn_PRE_DIV(config->alt_pre_div)); + imx8m_clock_set_target_val(IMX8M_DRAM_APB_CLK_ROOT, + IMX8M_CCM_TARGET_ROOTn_ENABLE | + IMX8M_CCM_TARGET_ROOTn_MUX(config->apb_root_sel) | + IMX8M_CCM_TARGET_ROOTn_PRE_DIV(config->apb_pre_div)); + 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)); +} + +struct imx_int_pll_rate_table { + u32 rate; + u32 r1; + u32 r2; +}; + +#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 struct imx_int_pll_rate_table imx8mm_fracpll_tbl[] = { + { .rate = 1000000000U, .r1 = MDIV(250) | PDIV(3) | SDIV(1), .r2 = 0 }, + { .rate = 800000000U, .r1 = MDIV(300) | PDIV(9) | SDIV(0), .r2 = 0 }, + { .rate = 750000000U, .r1 = MDIV(250) | PDIV(8) | SDIV(0), .r2 = 0 }, + { .rate = 650000000U, .r1 = MDIV(325) | PDIV(3) | SDIV(2), .r2 = 0 }, + { .rate = 600000000U, .r1 = MDIV(300) | PDIV(3) | SDIV(2), .r2 = 0 }, + { .rate = 594000000U, .r1 = MDIV( 99) | PDIV(1) | SDIV(2), .r2 = 0 }, + { .rate = 400000000U, .r1 = MDIV(300) | PDIV(9) | SDIV(1), .r2 = 0 }, + { .rate = 266666667U, .r1 = MDIV(400) | PDIV(9) | SDIV(2), .r2 = 0 }, + { .rate = 167000000U, .r1 = MDIV(334) | PDIV(3) | SDIV(4), .r2 = 0 }, + { .rate = 100000000U, .r1 = MDIV(300) | PDIV(9) | SDIV(3), .r2 = 0 }, +}; + +static struct imx_int_pll_rate_table *fracpll(u32 freq) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(imx8mm_fracpll_tbl); i++) + if (freq == imx8mm_fracpll_tbl[i].rate) + return &imx8mm_fracpll_tbl[i]; + + return NULL; +} + +static int dram_pll_init(u32 freq) +{ + volatile int i; + u32 tmp; + void *pll_base; + struct imx_int_pll_rate_table *rate; + + rate = fracpll(freq); + if (!rate) { + printf("No matched freq table %u\n", freq); + 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(rate->r1, pll_base + 4); + writel(rate->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; +} + +void ddrphy_init_set_dfi_clk(unsigned int drate) +{ + switch (drate) { + case 4000: + dram_pll_init(MHZ(1000)); + dram_disable_bypass(); + break; + case 3200: + dram_pll_init(MHZ(800)); + dram_disable_bypass(); + break; + case 3000: + dram_pll_init(MHZ(750)); + dram_disable_bypass(); + break; + case 2400: + dram_pll_init(MHZ(600)); + dram_disable_bypass(); + break; + case 1600: + dram_pll_init(MHZ(400)); + dram_disable_bypass(); + break; + case 1066: + dram_pll_init(MHZ(266)); + dram_disable_bypass(); + break; + case 667: + dram_pll_init(MHZ(167)); + dram_disable_bypass(); + break; + case 400: + dram_enable_bypass(MHZ(400)); + break; + case 100: + dram_enable_bypass(MHZ(100)); + break; + default: + return; + } +} + +void ddrphy_init_read_msg_block(enum fw_type type) +{ +} |