/* * Copyright (C) 2014-2015 Apple Inc. All rights reserved. * * This document is the property of Apple Inc. * It is considered confidential and proprietary. * * This document may not be reproduced or transmitted in any form, * in whole or in part, without the express written permission of * Apple Inc. */ /* * Init Sequence Library for use in: * Driver for the LPDDR4 compatible DRAM Control Subsystem (DCS) block */ /* Summary of Steps from the Spec (Spreadsheet) + (Aa) Steps 0,1,2,3a [need to identify parameterization] + (Ab) Step 3b [PMGR: set Freq to 50MHz & delay-wait] + (Ac) Steps 3c,4,5 [need to identify parameterization] + (B) Step 6 [Change Clk Speed] + (C) Step 7 [include delay-wait from early Step 8] + (D) Step 8a [Cal: CA 800MHz Vmin] + (E) Step 8b [Write Regs] + (F) Step 8c [Cal: CA 800MHz Vmin] (...again? should it be CS?) + (G) Step 8d [Write Regs] + (H) Step 9 [Write Regs] + (I) Step 10 [Cal: WrLvl, RdDq, WrDq 800MHz Vmin] + (J) Step 11 [Write Regs] + (K) Step 12 [Cal: CA 1200MHz Vnom] + (L) Step 13 [Write Regs] + (M) Step 14 [Cal: WrLvl, RdDq, WrDq 1200MHz Vmin] + (N) Steps 15,16,17,18,19,20 [need to identify parameterization] Between these steps there is possibly a need/desire for: - Frequency Changes (PMGR) - PMU save/restore - debug messages - register dumps (DV, SiVal) - anything else! CONFIG Table: Compile-Time Known: Type of Execution Environment CoreOS, DV, SiVal Type of Target SoC, FPGA, Palladium ChipID 0x8000, 0x8001, etc. Revision A0, B0, B1, etc. Num DCS 4, 8, etc. (Derived...!) Variant Flags Work-arounds, RADAR Ref., et al. (Config Info know at Compile time can be subject to #if) Run-Time Known: Type of Init ColdBoot, AOP_DDR, AOP_AWAKE Debug Flags Low-Level, Milestone, et al. RegValue Table Much like today's: various values for various registers Sensitive to ChipID (also Target type?) (Internal static alloc (Complete Table Fill-in within Init function): Fill in Values (per ChipID/Rev & Target-type) return pointer to caller (Allows customization by caller before use) TODO: - Convert to using SPDS for register names, et al */ //================================================================================ // API Definitions #include dcs_config_t dcs_cfg; extern dcs_config_params_t dcs_params; #if izIBOOT int debug_level_dcs = DEBUG_SPEW; #endif //================================================================================ // Configure the Library for use for the current Boot Type, Chip Type, etc. //================================================================================ bool dcs_init_config(dcs_boottype_t boot_type, uint32_t chipID, uint32_t chipRev, uint32_t num_dcs, uint32_t num_rank) { dcs_cfg.valid = false; // TODO: Get a little noisy about failures in this function // Validate that Build Environment is one that we support if (!(izIBOOT || izSIVAL || izDV || izPERTOS)) { return false; } // Validate that Target Type is one that we support if (!(izSOC || izFPGA || izPAL)) return false; dcs_cfg.boot_type = boot_type; // Validate that the Boot Type is one that we support if (!(izColdBoot || izResume || izAOP_DDR || izAOP_Awake)) return false; // Validate that we can support the requested number of channels/ranks if ((num_dcs > DCS_NUM_CHANNELS) || (num_rank > DCS_NUM_RANKS)) return false; #if izIBOOT // Validate the Chip ID provided against the actual (only for iBoot) if (chipID != platform_get_chip_id()) return false; #endif dcs_cfg.chipID = chipID; dcs_cfg.chipRev = chipRev; dcs_cfg.num_dcs = num_dcs; dcs_cfg.num_rank = num_rank; dcs_cfg.debugBitz = DCS_DBG_DEFAULT; // Default include Overview dcs_cfg.variantBitz = DCS_VARIANT_NONE; // Default: No variant flags enabled #if 1 // Very useful indicator that we have built this library correctly dbgprintf(DCS_DBG_OVERVIEW, "DCS Init Lib Built for: " #if izIBOOT "iBoot " #endif #if izSIVAL "SiVal " #endif #if izDV "DV " #endif #if izPERTOS "PERTOS " #endif #if izSOC "SOC" #endif #if izFPGA "FPGA" #endif #if izPAL "PAL" #endif "\n"); #endif dbgprintf(DCS_DBG_OVERVIEW, "DCS Init [%s Build] for ChipID==0x%04x chipRev %c%c (#DCS==%d, #Rank==%d) [%s] using %s\n", (izIBOOT)? "iBoot":(izSIVAL)? "SiVal":(izDV)? "DV":(izPERTOS)? "PERTOS":"??Build??", chipID, ((chipRev >> 4) & 0xF) + 'A', ((chipRev >> 0) & 0xF) + '0', dcs_cfg.num_dcs, dcs_cfg.num_rank, (izSOC)? "SoC":(izFPGA)? "FPGA":(izPAL)? "Palladium":"??Target??", (izColdBoot)? "Coldboot":(izResume)? "Resume":(izAOP_DDR)? "AOP_DDR":(izAOP_Awake)? "AOP_Awake":"??BootType??"); // We're good to go! dcs_cfg.valid = true; return dcs_cfg.valid; } //================================================================================ // Alter the Debug or the Variant Bits, returning the value-before-modifcation //================================================================================ uint32_t dcs_init_debug_enable(uint32_t bit) { uint32_t ret = dcs_cfg.debugBitz; dcs_cfg.debugBitz |= bit; return ret; } uint32_t dcs_init_debug_disable(uint32_t bit) { uint32_t ret = dcs_cfg.debugBitz; dcs_cfg.debugBitz &= ~bit; return ret; } uint32_t dcs_init_variant_enable(uint32_t bit) { uint32_t ret = dcs_cfg.variantBitz; dcs_cfg.variantBitz |= bit; return ret; } uint32_t dcs_init_variant_disable(uint32_t bit) { uint32_t ret = dcs_cfg.variantBitz; dcs_cfg.variantBitz &= ~bit; return ret; } //================================================================================ // Set up the Config Parameter Table (specifc to Platform, using target type) //================================================================================ dcs_config_params_t *dcs_init_param_table(void) { // Call (platform-specific) function to initialize values to be used during DCS Init dcs_init_config_params(&dcs_params, DCS_TARGET_TYPE); // Return pointer to the table // (allows caller to optionally modify values for possible experimentation) return &dcs_params; } //================================================================================ // Basic Register Write/Poll Functions, including MR Cmds //================================================================================ void dcs_reg_write(uintptr_t reg_addr, uint32_t reg_value) { dbgprintf(DCS_DBG_REG_ACCESS, "DCS Init REG(%p) <- 0x%08X (Single-Reg-Write)\n", (void *)reg_addr, reg_value); DCS_REG_ACCESS(reg_addr) = reg_value; } void dcs_reg_poll(uintptr_t reg_addr, uint32_t mask_value, uint32_t required_value) { dbgprintf(DCS_DBG_REG_ACCESS, "DCS Init REG(%p) Poll: Mask (0x%08X) == 0x%08x (Single-Reg-Poll)\n", (void *)reg_addr, mask_value, required_value); SPIN_W_TMO_WHILE((DCS_REG_ACCESS(reg_addr) & mask_value) != required_value); } void dcs_reg_write_all_chan(uintptr_t reg_addr, uint32_t reg_value) { uint32_t channel; dbgprintf(DCS_DBG_REG_ACCESS, "DCS Init REG(%p) <- 0x%08X (x%d)", (void *)reg_addr, reg_value, dcs_cfg.num_dcs); for(channel = 0; channel < dcs_cfg.num_dcs; channel++) { DCS_REG_ACCESS(reg_addr + (channel * DCS_SPACING)) = reg_value; dbgprintf(DCS_DBG_CHAN_DETAIL, "%d ", channel); } dbgprintf(DCS_DBG_REG_ACCESS, "\n"); } void dcs_reg_poll_all_chan(uintptr_t reg_addr, uint32_t mask_value, uint32_t required_value) { uint32_t channel; dbgprintf(DCS_DBG_REG_ACCESS, "DCS Init REG(%p) Poll: Mask (0x%08X) == 0x%08x ...", (void *)reg_addr, mask_value, required_value); for(channel = 0; channel < dcs_cfg.num_dcs; channel++) { dbgprintf(DCS_DBG_CHAN_DETAIL, " %d", channel); SPIN_W_TMO_WHILE((DCS_REG_READ_CH(channel, reg_addr) & mask_value) != required_value); dbgprintf(DCS_DBG_CHAN_DETAIL, "!"); } dbgprintf(DCS_DBG_REG_ACCESS, "\n"); } // To determine whether to use dcs_reg_write_all_chan or dcs_reg_write, // because regs within a dcs hw block will be duplicated per channel, while those outside the block will not bool dcs_reg_is_outside_dcs_block(uintptr_t reg_addr) { if((reg_addr >= DCS_BASE_ADDR) && (reg_addr < (DCS_BASE_ADDR + DCS_SPACING))) return false; return true; } // Only send the cmd to specific channel and rank (no poll afterward) static void dcs_mrcmd_send_specific_ch_rnk(uint32_t cmd, uint8_t channel, uint8_t rank) { cmd |= (rank << MRCMD_RANK_SHIFT); rDCS_MCU_MRINITCMD(channel) = cmd; } // Only poll the specific channel and rank (return cmd word) static uint32_t dcs_mrcmd_poll_specific_ch_rnk(uint32_t cmd, uint8_t channel, uint8_t rank) { SPIN_W_TMO_WHILE((cmd = rDCS_MCU_MRINITSTS(channel)) & MRCMD_POLL_BIT); return cmd; } // Only send the cmd to specific channel and rank static uint32_t dcs_mrcmd_to_specific_ch_rnk(uint32_t cmd, uint8_t channel, uint8_t rank) { dcs_mrcmd_send_specific_ch_rnk(cmd, channel, rank); return dcs_mrcmd_poll_specific_ch_rnk(cmd, channel, rank); } // Send the cmd to channel and rank as indicated (used during calibration on H6 and later) static uint32_t dcs_form_mrcmd_word(dcs_mrcmd_op_t op, int reg, uintptr_t val) { uint32_t cmd; if (op == MR_READ) cmd = MRCMD_TYPE_READ | (reg << MRCMD_REG_SHIFT); else if (op == MR_WRITE) cmd = MRCMD_TYPE_WRITE | (reg << MRCMD_REG_SHIFT) | (((uint32_t)val) << MRCMD_DATA_SHIFT); // MPC else cmd = MRCMD_TYPE_MPC | (reg << MRCMD_REG_SHIFT) | (((uint32_t)val) << MRCMD_DATA_SHIFT); return cmd; } // Send the cmd to channel and rank as indicated (used during calibration on H6 and later) void dcs_mrcmd_to_ch_rnk(dcs_mrcmd_op_t op, uint8_t channel, uint8_t rank, int reg, uintptr_t val) { uint32_t cmd, regval; uint8_t *buffer = (uint8_t *) val; cmd = dcs_form_mrcmd_word(op, reg, val); regval = dcs_mrcmd_to_specific_ch_rnk(cmd, channel, rank); if (op == MR_READ) { dbgprintf(DCS_DBG_MR_READ, " [data==0x%02x] ", (regval >> MRCMD_DATA_SHIFT) & 0xff); *buffer++ = (regval >> MRCMD_DATA_SHIFT) & 0xff; } } void dcs_mrcmd(dcs_mrcmd_op_t op, uint8_t channels, uint8_t ranks, int reg, uintptr_t val) { uint8_t ch, r; uint32_t cmd, regval; uint8_t *buffer = (uint8_t *) val; cmd = dcs_form_mrcmd_word(op, reg, val); dbgprintf(DCS_DBG_REG_ACCESS, "DCS Init MRCMD(0x%08X)[%dx chan][%dx rank]", cmd, channels, ranks); dbgprintf(DCS_DBG_CHAN_DETAIL, " SEND: "); for (ch = 0; ch < channels; ch++) { for (r = 0; r < ranks; r++) { dbgprintf(DCS_DBG_CHAN_DETAIL, "->%d%d ", ch, r); dcs_mrcmd_send_specific_ch_rnk(cmd, ch, r); } } dbgprintf(DCS_DBG_CHAN_DETAIL, " POLL: "); for (ch = 0; ch < channels; ch++) { for (r = 0; r < ranks; r++) { regval = dcs_mrcmd_poll_specific_ch_rnk(cmd, ch, r); dbgprintf(DCS_DBG_CHAN_DETAIL, "%d%d! ", ch, r); if (op == MR_READ) { dbgprintf(DCS_DBG_MR_READ, " [data==0x%02x] ", (regval >> MRCMD_DATA_SHIFT) & 0xff); *buffer++ = (regval >> MRCMD_DATA_SHIFT) & 0xff; } } } dbgprintf(DCS_DBG_REG_ACCESS, "\n"); } //================================================================================ // Utility Functions //================================================================================ void dcs_spin(uint32_t duration_us, char *reason) { if (reason == NULL) reason = ""; dbgprintf(DCS_DBG_SPIN, "DCS Init Spin %d us %s\n", duration_us, reason); delay_for_us(duration_us); } //================================================================================ // Apply Tunables (uses callout that is specifc to Platform) //================================================================================ void dcs_init_apply_tunables(void) { volatile uintptr_t regptr = 0; uint32_t regval; uint32_t i,numtune = 0; const struct dcs_tunable *dcs_tune; dcs_tune = dcs_init_tunable_table(&numtune); // Check that we have a table if ((dcs_tune == NULL) || (numtune == 0)) return; for (i = 0; i < numtune; i++, dcs_tune++) { // end of list? if (dcs_tune->reg == 0) break; // Check if we are now looking at a new register if (dcs_tune->reg != regptr) { // Write out regval to previous register (if there was one) if (regptr) { if(dcs_reg_is_outside_dcs_block(regptr)) dcs_reg_write(regptr, regval); else dcs_reg_write_all_chan(regptr, regval); } // read in the current register value (READ from CHAN zero, write to ALL CHAN) regptr = dcs_tune->reg; regval = *((uint32_t *)regptr); } // Modify the regval for the Bits indicated in the Mask regval &= ~dcs_tune->mask; regval |= dcs_tune->value; } // (Final) Write out regval to previous register (if there was one) if (regptr) { if(dcs_reg_is_outside_dcs_block(regptr)) dcs_reg_write(regptr, regval); else dcs_reg_write_all_chan(regptr, regval); } #if SUB_PLATFORM_S8001 if (dcs_cfg.chipRev < CHIP_REVISION_B0) { dcs_reg_write_all_chan(rDCS_MCU_ROWHAM_CTL(1), 0x4A3822D4); } #endif }