| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2019 |
| * Author(s): Giulio Benetti <giulio.benetti@benettiengineering.com> |
| */ |
| |
| #include <common.h> |
| #include <clk.h> |
| #include <dm.h> |
| #include <dm/device_compat.h> |
| #include <init.h> |
| #include <log.h> |
| #include <ram.h> |
| #include <asm/io.h> |
| #include <linux/bitops.h> |
| #include <linux/delay.h> |
| #include <linux/err.h> |
| |
| /* SDRAM Command Code */ |
| #define SD_CC_ARD 0x0 /* Master Bus (AXI) command - Read */ |
| #define SD_CC_AWR 0x1 /* Master Bus (AXI) command - Write */ |
| #define SD_CC_IRD 0x8 /* IP command - Read */ |
| #define SD_CC_IWR 0x9 /* IP command - Write */ |
| #define SD_CC_IMS 0xA /* IP command - Set Mode Register */ |
| #define SD_CC_IACT 0xB /* IP command - ACTIVE */ |
| #define SD_CC_IAF 0xC /* IP command - Auto Refresh */ |
| #define SD_CC_ISF 0xD /* IP Command - Self Refresh */ |
| #define SD_CC_IPRE 0xE /* IP command - Precharge */ |
| #define SD_CC_IPREA 0xF /* IP command - Precharge ALL */ |
| |
| #define SEMC_MCR_MDIS BIT(1) |
| #define SEMC_MCR_DQSMD BIT(2) |
| |
| #define SEMC_INTR_IPCMDERR BIT(1) |
| #define SEMC_INTR_IPCMDDONE BIT(0) |
| |
| #define SEMC_IPCMD_KEY 0xA55A0000 |
| |
| struct imxrt_semc_regs { |
| /* 0x0 */ |
| u32 mcr; |
| u32 iocr; |
| u32 bmcr0; |
| u32 bmcr1; |
| u32 br[9]; |
| |
| /* 0x34 */ |
| u32 res1; |
| u32 inten; |
| u32 intr; |
| /* 0x40 */ |
| u32 sdramcr0; |
| u32 sdramcr1; |
| u32 sdramcr2; |
| u32 sdramcr3; |
| /* 0x50 */ |
| u32 nandcr0; |
| u32 nandcr1; |
| u32 nandcr2; |
| u32 nandcr3; |
| /* 0x60 */ |
| u32 norcr0; |
| u32 norcr1; |
| u32 norcr2; |
| u32 norcr3; |
| /* 0x70 */ |
| u32 sramcr0; |
| u32 sramcr1; |
| u32 sramcr2; |
| u32 sramcr3; |
| /* 0x80 */ |
| u32 dbicr0; |
| u32 dbicr1; |
| u32 res2[2]; |
| /* 0x90 */ |
| u32 ipcr0; |
| u32 ipcr1; |
| u32 ipcr2; |
| u32 ipcmd; |
| /* 0xA0 */ |
| u32 iptxdat; |
| u32 res3[3]; |
| /* 0xB0 */ |
| u32 iprxdat; |
| u32 res4[3]; |
| /* 0xC0 */ |
| u32 sts[16]; |
| }; |
| |
| #if !defined(TARGET_IMXRT1170_EVK) |
| #define SEMC_IOCR_MUX_A8_SHIFT 0 |
| #define SEMC_IOCR_MUX_CSX0_SHIFT 3 |
| #define SEMC_IOCR_MUX_CSX1_SHIFT 6 |
| #define SEMC_IOCR_MUX_CSX2_SHIFT 9 |
| #define SEMC_IOCR_MUX_CSX3_SHIFT 12 |
| #define SEMC_IOCR_MUX_RDY_SHIFT 15 |
| #else |
| #define SEMC_IOCR_MUX_A8_SHIFT 0 |
| #define SEMC_IOCR_MUX_CSX0_SHIFT 4 |
| #define SEMC_IOCR_MUX_CSX1_SHIFT 8 |
| #define SEMC_IOCR_MUX_CSX2_SHIFT 12 |
| #define SEMC_IOCR_MUX_CSX3_SHIFT 16 |
| #define SEMC_IOCR_MUX_RDY_SHIFT 20 |
| #endif |
| |
| struct imxrt_sdram_mux { |
| u8 a8; |
| u8 csx0; |
| u8 csx1; |
| u8 csx2; |
| u8 csx3; |
| u8 rdy; |
| }; |
| |
| #define SEMC_SDRAMCR0_PS_SHIFT 0 |
| #define SEMC_SDRAMCR0_BL_SHIFT 4 |
| #define SEMC_SDRAMCR0_COL_SHIFT 8 |
| #define SEMC_SDRAMCR0_CL_SHIFT 10 |
| |
| struct imxrt_sdram_control { |
| u8 memory_width; |
| u8 burst_len; |
| u8 no_columns; |
| u8 cas_latency; |
| }; |
| |
| #define SEMC_SDRAMCR1_PRE2ACT_SHIFT 0 |
| #define SEMC_SDRAMCR1_ACT2RW_SHIFT 4 |
| #define SEMC_SDRAMCR1_RFRC_SHIFT 8 |
| #define SEMC_SDRAMCR1_WRC_SHIFT 13 |
| #define SEMC_SDRAMCR1_CKEOFF_SHIFT 16 |
| #define SEMC_SDRAMCR1_ACT2PRE_SHIFT 20 |
| |
| #define SEMC_SDRAMCR2_SRRC_SHIFT 0 |
| #define SEMC_SDRAMCR2_REF2REF_SHIFT 8 |
| #define SEMC_SDRAMCR2_ACT2ACT_SHIFT 16 |
| #define SEMC_SDRAMCR2_ITO_SHIFT 24 |
| |
| #define SEMC_SDRAMCR3_REN BIT(0) |
| #define SEMC_SDRAMCR3_REBL_SHIFT 1 |
| #define SEMC_SDRAMCR3_PRESCALE_SHIFT 8 |
| #define SEMC_SDRAMCR3_RT_SHIFT 16 |
| #define SEMC_SDRAMCR3_UT_SHIFT 24 |
| |
| struct imxrt_sdram_timing { |
| u8 pre2act; |
| u8 act2rw; |
| u8 rfrc; |
| u8 wrc; |
| u8 ckeoff; |
| u8 act2pre; |
| |
| u8 srrc; |
| u8 ref2ref; |
| u8 act2act; |
| u8 ito; |
| |
| u8 rebl; |
| u8 prescale; |
| u8 rt; |
| u8 ut; |
| }; |
| |
| enum imxrt_semc_bank { |
| SDRAM_BANK1, |
| SDRAM_BANK2, |
| SDRAM_BANK3, |
| SDRAM_BANK4, |
| MAX_SDRAM_BANK, |
| }; |
| |
| #define SEMC_BR_VLD_MASK 1 |
| #define SEMC_BR_MS_SHIFT 1 |
| |
| struct bank_params { |
| enum imxrt_semc_bank target_bank; |
| u32 base_address; |
| u32 memory_size; |
| }; |
| |
| struct imxrt_sdram_params { |
| struct imxrt_semc_regs *base; |
| |
| struct imxrt_sdram_mux *sdram_mux; |
| struct imxrt_sdram_control *sdram_control; |
| struct imxrt_sdram_timing *sdram_timing; |
| |
| struct bank_params bank_params[MAX_SDRAM_BANK]; |
| u8 no_sdram_banks; |
| }; |
| |
| static int imxrt_sdram_wait_ipcmd_done(struct imxrt_semc_regs *regs) |
| { |
| do { |
| readl(®s->intr); |
| |
| if (regs->intr & SEMC_INTR_IPCMDDONE) |
| return 0; |
| if (regs->intr & SEMC_INTR_IPCMDERR) |
| return -EIO; |
| |
| mdelay(50); |
| } while (1); |
| } |
| |
| static int imxrt_sdram_ipcmd(struct imxrt_semc_regs *regs, u32 mem_addr, |
| u32 ipcmd, u32 wd, u32 *rd) |
| { |
| int ret; |
| |
| if (ipcmd == SD_CC_IWR || ipcmd == SD_CC_IMS) |
| writel(wd, ®s->iptxdat); |
| |
| /* set slave address for every command as specified on RM */ |
| writel(mem_addr, ®s->ipcr0); |
| |
| /* execute command */ |
| writel(SEMC_IPCMD_KEY | ipcmd, ®s->ipcmd); |
| |
| ret = imxrt_sdram_wait_ipcmd_done(regs); |
| if (ret < 0) |
| return ret; |
| |
| if (ipcmd == SD_CC_IRD) { |
| if (!rd) |
| return -EINVAL; |
| |
| *rd = readl(®s->iprxdat); |
| } |
| |
| return 0; |
| } |
| |
| int imxrt_sdram_init(struct udevice *dev) |
| { |
| struct imxrt_sdram_params *params = dev_get_plat(dev); |
| struct imxrt_sdram_mux *mux = params->sdram_mux; |
| struct imxrt_sdram_control *ctrl = params->sdram_control; |
| struct imxrt_sdram_timing *time = params->sdram_timing; |
| struct imxrt_semc_regs *regs = params->base; |
| struct bank_params *bank_params; |
| u32 rd; |
| int i; |
| |
| /* enable the SEMC controller */ |
| clrbits_le32(®s->mcr, SEMC_MCR_MDIS); |
| /* set DQS mode from DQS pad */ |
| setbits_le32(®s->mcr, SEMC_MCR_DQSMD); |
| |
| for (i = 0, bank_params = params->bank_params; |
| i < params->no_sdram_banks; bank_params++, |
| i++) |
| writel((bank_params->base_address & 0xfffff000) |
| | bank_params->memory_size << SEMC_BR_MS_SHIFT |
| | SEMC_BR_VLD_MASK, |
| ®s->br[bank_params->target_bank]); |
| |
| writel(mux->a8 << SEMC_IOCR_MUX_A8_SHIFT |
| | mux->csx0 << SEMC_IOCR_MUX_CSX0_SHIFT |
| | mux->csx1 << SEMC_IOCR_MUX_CSX1_SHIFT |
| | mux->csx2 << SEMC_IOCR_MUX_CSX2_SHIFT |
| | mux->csx3 << SEMC_IOCR_MUX_CSX3_SHIFT |
| | mux->rdy << SEMC_IOCR_MUX_RDY_SHIFT, |
| ®s->iocr); |
| |
| writel(ctrl->memory_width << SEMC_SDRAMCR0_PS_SHIFT |
| | ctrl->burst_len << SEMC_SDRAMCR0_BL_SHIFT |
| | ctrl->no_columns << SEMC_SDRAMCR0_COL_SHIFT |
| | ctrl->cas_latency << SEMC_SDRAMCR0_CL_SHIFT, |
| ®s->sdramcr0); |
| |
| writel(time->pre2act << SEMC_SDRAMCR1_PRE2ACT_SHIFT |
| | time->act2rw << SEMC_SDRAMCR1_ACT2RW_SHIFT |
| | time->rfrc << SEMC_SDRAMCR1_RFRC_SHIFT |
| | time->wrc << SEMC_SDRAMCR1_WRC_SHIFT |
| | time->ckeoff << SEMC_SDRAMCR1_CKEOFF_SHIFT |
| | time->act2pre << SEMC_SDRAMCR1_ACT2PRE_SHIFT, |
| ®s->sdramcr1); |
| |
| writel(time->srrc << SEMC_SDRAMCR2_SRRC_SHIFT |
| | time->ref2ref << SEMC_SDRAMCR2_REF2REF_SHIFT |
| | time->act2act << SEMC_SDRAMCR2_ACT2ACT_SHIFT |
| | time->ito << SEMC_SDRAMCR2_ITO_SHIFT, |
| ®s->sdramcr2); |
| |
| writel(time->rebl << SEMC_SDRAMCR3_REBL_SHIFT |
| | time->prescale << SEMC_SDRAMCR3_PRESCALE_SHIFT |
| | time->rt << SEMC_SDRAMCR3_RT_SHIFT |
| | time->ut << SEMC_SDRAMCR3_UT_SHIFT |
| | SEMC_SDRAMCR3_REN, |
| ®s->sdramcr3); |
| |
| writel(2, ®s->ipcr1); |
| |
| for (i = 0, bank_params = params->bank_params; |
| i < params->no_sdram_banks; bank_params++, |
| i++) { |
| mdelay(250); |
| imxrt_sdram_ipcmd(regs, bank_params->base_address, SD_CC_IPREA, |
| 0, &rd); |
| imxrt_sdram_ipcmd(regs, bank_params->base_address, SD_CC_IAF, |
| 0, &rd); |
| imxrt_sdram_ipcmd(regs, bank_params->base_address, SD_CC_IAF, |
| 0, &rd); |
| imxrt_sdram_ipcmd(regs, bank_params->base_address, SD_CC_IMS, |
| ctrl->burst_len | (ctrl->cas_latency << 4), |
| &rd); |
| mdelay(250); |
| } |
| |
| return 0; |
| } |
| |
| static int imxrt_semc_of_to_plat(struct udevice *dev) |
| { |
| struct imxrt_sdram_params *params = dev_get_plat(dev); |
| ofnode bank_node; |
| u8 bank = 0; |
| |
| params->sdram_mux = |
| (struct imxrt_sdram_mux *) |
| dev_read_u8_array_ptr(dev, |
| "fsl,sdram-mux", |
| sizeof(struct imxrt_sdram_mux)); |
| if (!params->sdram_mux) { |
| pr_err("fsl,sdram-mux not found"); |
| return -EINVAL; |
| } |
| |
| params->sdram_control = |
| (struct imxrt_sdram_control *) |
| dev_read_u8_array_ptr(dev, |
| "fsl,sdram-control", |
| sizeof(struct imxrt_sdram_control)); |
| if (!params->sdram_control) { |
| pr_err("fsl,sdram-control not found"); |
| return -EINVAL; |
| } |
| |
| params->sdram_timing = |
| (struct imxrt_sdram_timing *) |
| dev_read_u8_array_ptr(dev, |
| "fsl,sdram-timing", |
| sizeof(struct imxrt_sdram_timing)); |
| if (!params->sdram_timing) { |
| pr_err("fsl,sdram-timing not found"); |
| return -EINVAL; |
| } |
| |
| dev_for_each_subnode(bank_node, dev) { |
| struct bank_params *bank_params; |
| char *bank_name; |
| int ret; |
| |
| /* extract the bank index from DT */ |
| bank_name = (char *)ofnode_get_name(bank_node); |
| strsep(&bank_name, "@"); |
| if (!bank_name) { |
| pr_err("missing sdram bank index"); |
| return -EINVAL; |
| } |
| |
| bank_params = ¶ms->bank_params[bank]; |
| strict_strtoul(bank_name, 10, |
| (unsigned long *)&bank_params->target_bank); |
| if (bank_params->target_bank >= MAX_SDRAM_BANK) { |
| pr_err("Found bank %d , but only bank 0,1,2,3 are supported", |
| bank_params->target_bank); |
| return -EINVAL; |
| } |
| |
| ret = ofnode_read_u32(bank_node, |
| "fsl,memory-size", |
| &bank_params->memory_size); |
| if (ret < 0) { |
| pr_err("fsl,memory-size not found"); |
| return -EINVAL; |
| } |
| |
| ret = ofnode_read_u32(bank_node, |
| "fsl,base-address", |
| &bank_params->base_address); |
| if (ret < 0) { |
| pr_err("fsl,base-address not found"); |
| return -EINVAL; |
| } |
| |
| debug("Found bank %s %u\n", bank_name, |
| bank_params->target_bank); |
| bank++; |
| } |
| |
| params->no_sdram_banks = bank; |
| debug("%s, no of banks = %d\n", __func__, params->no_sdram_banks); |
| |
| return 0; |
| } |
| |
| static int imxrt_semc_probe(struct udevice *dev) |
| { |
| struct imxrt_sdram_params *params = dev_get_plat(dev); |
| int ret; |
| fdt_addr_t addr; |
| |
| addr = dev_read_addr(dev); |
| if (addr == FDT_ADDR_T_NONE) |
| return -EINVAL; |
| |
| params->base = (struct imxrt_semc_regs *)addr; |
| |
| #ifdef CONFIG_CLK |
| struct clk clk; |
| |
| ret = clk_get_by_index(dev, 0, &clk); |
| if (ret < 0) |
| return ret; |
| |
| ret = clk_enable(&clk); |
| |
| if (ret) { |
| dev_err(dev, "failed to enable clock\n"); |
| return ret; |
| } |
| #endif |
| ret = imxrt_sdram_init(dev); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| static int imxrt_semc_get_info(struct udevice *dev, struct ram_info *info) |
| { |
| return 0; |
| } |
| |
| static struct ram_ops imxrt_semc_ops = { |
| .get_info = imxrt_semc_get_info, |
| }; |
| |
| static const struct udevice_id imxrt_semc_ids[] = { |
| { .compatible = "fsl,imxrt-semc", .data = 0 }, |
| { } |
| }; |
| |
| U_BOOT_DRIVER(imxrt_semc) = { |
| .name = "imxrt_semc", |
| .id = UCLASS_RAM, |
| .of_match = imxrt_semc_ids, |
| .ops = &imxrt_semc_ops, |
| .of_to_plat = imxrt_semc_of_to_plat, |
| .probe = imxrt_semc_probe, |
| .plat_auto = sizeof(struct imxrt_sdram_params), |
| }; |