329 lines
9.3 KiB
C
329 lines
9.3 KiB
C
/*
|
|
* Copyright (C) 2009-2012 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.
|
|
*/
|
|
|
|
#include <debug.h>
|
|
#include <drivers/amc/amc.h>
|
|
#include <drivers/amc/amc_phy.h>
|
|
#include <drivers/dram.h>
|
|
#include <lib/libc.h>
|
|
#include <platform/chipid.h>
|
|
#include <sys.h>
|
|
|
|
#include "amg.h"
|
|
|
|
#define CA_PIVOTS_LIST_SIZE (7)
|
|
#define DQ_PIVOTS_LIST_SIZE (5)
|
|
|
|
static const int ca_pivot_ps[CA_PIVOTS_LIST_SIZE] = {-375, -250, -125, 0, 125, 250, 375};
|
|
static const int dq_pivot_ps[DQ_PIVOTS_LIST_SIZE] = {-500, -250, 0, 250, 500};
|
|
static struct amc_phy_calibration_data amg_calibration_data = {
|
|
.bit_time_ps = 2500,
|
|
.min_ca_window_width_ps = 250,
|
|
.min_dq_window_width_ps = 250,
|
|
.ca_pivot_step_size_ps = 125,
|
|
.dq_pivot_step_size_ps = 250,
|
|
.max_offset_setting = 63,
|
|
.min_offset_factor = 12,
|
|
.ca_pivots_list_size = CA_PIVOTS_LIST_SIZE,
|
|
.ca_pivot_ps = (int *)ca_pivot_ps,
|
|
.dq_pivots_list_size = DQ_PIVOTS_LIST_SIZE,
|
|
.dq_pivot_ps = (int *)dq_pivot_ps,
|
|
};
|
|
|
|
void amc_phy_init(bool resume)
|
|
{
|
|
int i;
|
|
|
|
// overwrite bit_time_ps if presented
|
|
if (amc_phy_params.bit_time_ps)
|
|
amg_calibration_data.bit_time_ps = amc_phy_params.bit_time_ps;
|
|
|
|
for (i = 0; i < AMC_NUM_CHANNELS; i++) {
|
|
int ch = AMG_INDEX_FOR_CHANNEL(i); // cope with hole in AMG address space
|
|
|
|
rAMG_CORE_CONFIG(ch) = amc_phy_params.core_config;
|
|
|
|
#if !SUPPORT_FPGA
|
|
// PHY ZQ calibration
|
|
rAMG_ZQ_CONF(ch) = 0x00ff0096; // <rdar://problem/8263127>
|
|
rAMG_ZQ_CONTROL(ch) = 0x00000009; // <rdar://problem/7502673>
|
|
rAMG_ZQ_CONTROL(ch) = 0x0000000b;
|
|
// Not testing the error bit. This will never happen in normal operation,
|
|
// and in fact _can't_ be set in H4P B0.
|
|
while ((rAMG_ZQ_STATUS(ch) & 0x80) == 0) ;
|
|
|
|
// Train master DLL
|
|
rAMG_DLL_CONTROL(ch) = 0x00110001;
|
|
rAMG_DLL_CONTROL(ch) = 0x00110005;
|
|
while ((rAMG_DLL_STATUS(ch) & 1) != 1) ;
|
|
// check for timeout
|
|
if ((rAMG_DLL_STATUS(ch) & 0x4) != 0)
|
|
for (;;) ;
|
|
|
|
if (amc_phy_params.flags & FLAG_AMG_PARAM_CAL_STATIC) {
|
|
// CA static offset programming; note it's too early to use
|
|
// MRRs, so this relies on the SOC fusing.
|
|
uint16_t fine_lock = (rAMG_DLL_STATUS(ch) >> 5) & 0x3ff;
|
|
uint32_t fine_unit = (amg_calibration_data.bit_time_ps) / fine_lock;
|
|
uint32_t addr_offset;
|
|
uint32_t vendor_id = chipid_get_memory_manufacturer();
|
|
switch (vendor_id) {
|
|
case JEDEC_MANUF_ID_SAMSUNG:
|
|
addr_offset = (110/fine_unit) | (1 << 6); /* -110ps */
|
|
break;
|
|
case JEDEC_MANUF_ID_ELPIDA:
|
|
addr_offset = (50/fine_unit) | (1 << 6); /* -50ps */
|
|
break;
|
|
case JEDEC_MANUF_ID_HYNIX:
|
|
addr_offset = 0; /* 0ps */
|
|
break;
|
|
default:
|
|
addr_offset = 0;
|
|
break;
|
|
}
|
|
rAMG_ADDR_OFFSET(ch) = addr_offset;
|
|
} else if (resume) {
|
|
amc_phy_calibration_restore_ca_offset(i);
|
|
}
|
|
|
|
rAMG_DLL_TIMER(ch) = 0x0003061a;
|
|
|
|
// set frequency scaling parameters
|
|
if (likely(!amc_phy_params.freq_scale))
|
|
rAMG_FREQ_SCALE(ch) = 0x00e40688;
|
|
else
|
|
rAMG_FREQ_SCALE(ch) = amc_phy_params.freq_scale;
|
|
|
|
#endif // !SUPPORT_FPGA
|
|
|
|
rAMG_READ_LATENCY(ch) = amc_phy_params.read_latency; // [linked to AMC phyrdwrtim]
|
|
rAMG_GATE_OFFSET(ch) = amc_phy_params.gate_offset;
|
|
|
|
rAMG_INIT_CONTROL(ch) = 0x00000001;
|
|
}
|
|
}
|
|
|
|
void amc_phy_scale_dll(int freqsel, int factor)
|
|
{
|
|
int i;
|
|
|
|
// Make sure AMG was inited.
|
|
// Assumptions: all supported channels are inited together,
|
|
// and channel 0 is always used.
|
|
if ((rAMG_INIT_CONTROL(0) & 1) == 0)
|
|
return;
|
|
|
|
// Scale the current lock value
|
|
for (i = 0; i < AMC_NUM_CHANNELS; i++) {
|
|
int ch = AMG_INDEX_FOR_CHANNEL(i);
|
|
uint16_t new_lock;
|
|
uint16_t fine_lock;
|
|
|
|
// The fine_lock is 10 bits and coarse is 8. Since the override
|
|
// is in coarse units, we do shifty math on the fine units.
|
|
fine_lock = (rAMG_DLL_STATUS(ch) >> 5) & 0x3ff;
|
|
if (factor == 1)
|
|
new_lock = fine_lock >> 2;
|
|
else if (factor == 8)
|
|
new_lock = fine_lock << 1;
|
|
else
|
|
panic("amc_phy_scale_dll: unsupported DLL scale factor");
|
|
if (new_lock > 0xff)
|
|
new_lock = 0xff;
|
|
rAMG_DLL_OVERRIDE(ch) = new_lock;
|
|
rAMG_DLL_CONTROL(ch) = 0x00110004;
|
|
while (!(rAMG_DLL_STATUS(ch) & 1)) ;
|
|
}
|
|
}
|
|
|
|
bool amc_phy_rddq_cal()
|
|
{
|
|
uint32_t *dq_cal_data;
|
|
bool result;
|
|
uint8_t index, i;
|
|
uint32_t device_width;
|
|
uint8_t slot, ch;
|
|
uint16_t fine_lock;
|
|
uint32_t fine_unit;
|
|
uint32_t offset;
|
|
|
|
result = false;
|
|
|
|
if (amc_phy_params.flags & FLAG_AMG_PARAM_CAL_STATIC) {
|
|
dq_cal_data = malloc(AMC_NUM_CHANNELS * 2 * sizeof(uint32_t));
|
|
|
|
if (chipid_get_memory_dqcal(dq_cal_data)) {
|
|
index = 0;
|
|
device_width = (amc_get_memory_device_info())->width;
|
|
|
|
for (i = 0; i < AMC_NUM_CHANNELS; i++) {
|
|
ch = AMG_INDEX_FOR_CHANNEL(i); // cope with hole in AMG address space
|
|
fine_lock = (rAMG_DLL_STATUS(ch) >> 5) & 0x3ff;
|
|
fine_unit = (amg_calibration_data.bit_time_ps) / fine_lock;
|
|
|
|
// Loop across all bytes twice, first for read then for write,
|
|
// which matches the order in which the data is given to us.
|
|
for (slot = 0; slot < device_width * 2; slot++) {
|
|
offset = (((dq_cal_data[index] & 0x8) << 3) |
|
|
((((dq_cal_data[index] & 0x7) * 40) / fine_unit) & 0x3f));
|
|
dq_cal_data[index] >>= 4;
|
|
if (slot < device_width)
|
|
rAMG_RD_OFFSET(ch, slot) = offset;
|
|
else
|
|
rAMG_WR_OFFSET(ch, (slot - device_width)) = offset;
|
|
// Bump at end of "device_width" bytes of read and write
|
|
if ((slot % device_width) == (device_width - 1))
|
|
index++;
|
|
}
|
|
}
|
|
|
|
result = true;
|
|
}
|
|
|
|
free(dq_cal_data);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void amc_phy_bypass_prep(int step)
|
|
{
|
|
// Make sure AMG was inited.
|
|
// Assumptions: all supported channels are inited together,
|
|
// and channel 0 is always used.
|
|
if ((rAMG_INIT_CONTROL(0) & 1) == 0)
|
|
return;
|
|
|
|
// We need to be sure the DLL is in forced mode. Reload the
|
|
// original full-speed lock result. <rdar://problem/7269959>
|
|
// Note the 0 is the unused freqsel argument.
|
|
amc_phy_scale_dll(0, 1);
|
|
}
|
|
|
|
void amc_phy_finalize()
|
|
{
|
|
int i;
|
|
|
|
// Enable pulse-mode DLL training
|
|
for (i = 0; i < AMC_NUM_CHANNELS; i++) {
|
|
int ch = AMG_INDEX_FOR_CHANNEL(i); // cope with hole in AMG address space
|
|
rAMG_DLL_FILTER(ch) = 0x0000002e;
|
|
rAMG_DLL_CONTROL(ch) = 0x00110006;
|
|
}
|
|
}
|
|
|
|
void amc_phy_enable_dqs_pulldown(bool enable)
|
|
{
|
|
}
|
|
|
|
void amc_phy_reset_fifos()
|
|
{
|
|
}
|
|
|
|
const struct amc_phy_calibration_data * amc_phy_get_calibration_data()
|
|
{
|
|
return &amg_calibration_data;
|
|
}
|
|
|
|
void amc_phy_calibration_init(uint32_t *fine_lock_ps)
|
|
{
|
|
uint8_t ch;
|
|
uint16_t lock_result;
|
|
const struct amc_memory_device_info *dev_info;
|
|
|
|
ASSERT(fine_lock_ps != NULL);
|
|
|
|
*fine_lock_ps = 0;
|
|
dev_info = amc_get_memory_device_info();
|
|
|
|
for (ch = 0; ch < AMC_NUM_CHANNELS; ch++) {
|
|
rAMG_DLL_FILTER(AMG_INDEX_FOR_CHANNEL(ch)) = 0;
|
|
|
|
lock_result = ((rAMG_DLL_STATUS(AMG_INDEX_FOR_CHANNEL(ch)) >> 5) & 0x3ff); // fine value
|
|
rAMG_DLL_OVERRIDE(AMG_INDEX_FOR_CHANNEL(ch)) = lock_result / 4; // coarse value
|
|
amc_phy_run_dll_update(ch);
|
|
|
|
*fine_lock_ps += (amg_calibration_data.bit_time_ps) / (lock_result);
|
|
|
|
// <rdar://problem/10996362> Modified guidance for AMG bit_sample field setting
|
|
if (((dev_info->vendor_id == JEDEC_MANUF_ID_SAMSUNG) && (dev_info->density == JEDEC_DENSITY_2Gb) && (dev_info->rev_id == 0)) ||
|
|
((dev_info->vendor_id == JEDEC_MANUF_ID_SAMSUNG) && (dev_info->density == JEDEC_DENSITY_4Gb) && (dev_info->rev_id == 1)) ||
|
|
((dev_info->vendor_id == JEDEC_MANUF_ID_HYNIX) && (dev_info->density == JEDEC_DENSITY_1Gb)))
|
|
rAMG_DQCAL_CONTROL(AMG_INDEX_FOR_CHANNEL(ch)) = 0x00001ffb;
|
|
else
|
|
rAMG_DQCAL_CONTROL(AMG_INDEX_FOR_CHANNEL(ch)) = 0x00003ffb;
|
|
}
|
|
|
|
*fine_lock_ps = *fine_lock_ps / AMC_NUM_CHANNELS;
|
|
}
|
|
|
|
void amc_phy_run_dll_update(uint8_t ch)
|
|
{
|
|
uint8_t ch_amg_idx = AMG_INDEX_FOR_CHANNEL(ch);
|
|
|
|
rAMG_DLL_CONTROL(ch_amg_idx) = 0x00110000 | (1 << 2) | (0 << 0);
|
|
while ((rAMG_DLL_STATUS(ch_amg_idx) & 1) == 0) ;
|
|
}
|
|
|
|
void amc_phy_set_addr_offset(uint8_t ch, uint8_t value)
|
|
{
|
|
rAMG_ADDR_OFFSET(AMG_INDEX_FOR_CHANNEL(ch)) = value;
|
|
}
|
|
|
|
void amc_phy_set_rd_offset(uint8_t ch, uint8_t byte, uint8_t value)
|
|
{
|
|
rAMG_RD_OFFSET(AMG_INDEX_FOR_CHANNEL(ch), byte) = value;
|
|
}
|
|
|
|
void amc_phy_set_wr_offset(uint8_t ch, uint8_t byte, uint8_t value)
|
|
{
|
|
rAMG_WR_OFFSET(AMG_INDEX_FOR_CHANNEL(ch), byte) = value;
|
|
}
|
|
|
|
void amc_phy_configure_dll_pulse_mode(uint8_t ch, bool enable)
|
|
{
|
|
if (!enable) {
|
|
rAMG_DLL_FILTER(AMG_INDEX_FOR_CHANNEL(ch)) = 0x0000000a;
|
|
}
|
|
else {
|
|
rAMG_DLL_FILTER(AMG_INDEX_FOR_CHANNEL(ch)) = 0x0000002e;
|
|
rAMG_DLL_CONTROL(AMG_INDEX_FOR_CHANNEL(ch)) = 0x00110006;
|
|
}
|
|
}
|
|
|
|
void amc_phy_run_dq_training(uint8_t ch)
|
|
{
|
|
uint8_t ch_amg_idx = AMG_INDEX_FOR_CHANNEL(ch);
|
|
|
|
rAMG_DQCAL_CONTROL(ch_amg_idx) |= (1 << 14); // start
|
|
while ((rAMG_DQCAL_STATUS(ch_amg_idx) & 1) == 0) ; // wait for 'Done' bit to set
|
|
}
|
|
|
|
uint32_t amc_phy_get_dq_training_status(uint8_t ch, uint8_t byte, uint8_t num_bytes)
|
|
{
|
|
uint8_t byte_result;
|
|
uint32_t status;
|
|
|
|
status = rAMG_DQCAL_STATUS(AMG_INDEX_FOR_CHANNEL(ch));
|
|
|
|
// bits[MAX_BYTE-1:0] of the byte_result field are for pos DQS strobe,
|
|
// and bits [2*MAX_BYTE-1:MAX_BYTE] for neg DQS strobe. Final result is
|
|
// 'AND' of two, for specified byte
|
|
byte_result = (status & (1 << (byte + 1)));
|
|
byte_result = byte_result && (status & (1 << (byte + num_bytes + 1)));
|
|
|
|
return byte_result;
|
|
}
|
|
|
|
void amc_phy_shift_dq_offset(int8_t *dq_offset, uint8_t num_bytes) {
|
|
amc_dram_shift_dq_offset(dq_offset, num_bytes);
|
|
}
|