blob: 086689382b72317f59efaf23e83ee306581fdfc1 [file] [log] [blame]
/*
* Copyright (C) 2018 Samsung Electronics Co., Ltd.
*
* Authors: Shaik Ameer Basha(shaik.ameer@samsung.com)
*
* Airbrush DDR Eye Margin tool.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*/
#include <linux/airbrush-sm-ctrl.h>
#include <linux/delay.h>
#include <linux/pci.h>
#include "airbrush-ddr.h"
#include "airbrush-ddr-internal.h"
#include "airbrush-pmic-ctrl.h"
#include "airbrush-regs.h"
static void ddrphy_set_read_offset(struct ab_ddr_context *ddr_ctx,
int offset_phy)
{
uint32_t ctrl_offsetr;
if (offset_phy < 0)
ctrl_offsetr = 0x100 | ((offset_phy * -1) & 0xff);
else
ctrl_offsetr = offset_phy;
ddr_reg_clr_set(ddr_ctx, DPHY_OFFSETR_CON0,
CTRL_OFFSETR0_MSK | CTRL_OFFSETR1_MSK,
CTRL_OFFSETR0(ctrl_offsetr) |
CTRL_OFFSETR1(ctrl_offsetr));
ddr_reg_clr_set(ddr_ctx, DPHY2_OFFSETR_CON0,
CTRL_OFFSETR0_MSK | CTRL_OFFSETR1_MSK,
CTRL_OFFSETR0(ctrl_offsetr) |
CTRL_OFFSETR1(ctrl_offsetr));
/* When CTRL_RESYNC bit transits from LOW to HIGH, pointers of FIFO
* within PHY and all of the DLL information (Read/Write/CA/CS DLL)
* is updated.
*/
ddr_reg_clr(ddr_ctx, DPHY_OFFSETD_CON0, CTRL_RESYNC);
ddr_reg_set(ddr_ctx, DPHY_OFFSETD_CON0, CTRL_RESYNC);
ddr_reg_clr(ddr_ctx, DPHY_OFFSETD_CON0, CTRL_RESYNC);
ddr_reg_clr(ddr_ctx, DPHY2_OFFSETD_CON0, CTRL_RESYNC);
ddr_reg_set(ddr_ctx, DPHY2_OFFSETD_CON0, CTRL_RESYNC);
ddr_reg_clr(ddr_ctx, DPHY2_OFFSETD_CON0, CTRL_RESYNC);
}
static void ddrphy_set_write_offset(struct ab_ddr_context *ddr_ctx,
int offset_phy)
{
uint32_t ctrl_offsetw;
if (offset_phy < 0)
ctrl_offsetw = 0x100 | ((offset_phy * -1) & 0xff);
else
ctrl_offsetw = offset_phy;
ddr_reg_clr_set(ddr_ctx, DPHY_OFFSETW_CON0,
CTRL_OFFSETW0_MSK | CTRL_OFFSETW1_MSK,
CTRL_OFFSETW0(ctrl_offsetw) |
CTRL_OFFSETW1(ctrl_offsetw));
ddr_reg_clr_set(ddr_ctx, DPHY2_OFFSETW_CON0,
CTRL_OFFSETW0_MSK | CTRL_OFFSETW1_MSK,
CTRL_OFFSETW0(ctrl_offsetw) |
CTRL_OFFSETW1(ctrl_offsetw));
/* When CTRL_RESYNC bit transits from LOW to HIGH, pointers of FIFO
* within PHY and all of the DLL information (Read/Write/CA/CS DLL)
* is updated.
*/
ddr_reg_clr(ddr_ctx, DPHY_OFFSETD_CON0, CTRL_RESYNC);
ddr_reg_set(ddr_ctx, DPHY_OFFSETD_CON0, CTRL_RESYNC);
ddr_reg_clr(ddr_ctx, DPHY_OFFSETD_CON0, CTRL_RESYNC);
ddr_reg_clr(ddr_ctx, DPHY2_OFFSETD_CON0, CTRL_RESYNC);
ddr_reg_set(ddr_ctx, DPHY2_OFFSETD_CON0, CTRL_RESYNC);
ddr_reg_clr(ddr_ctx, DPHY2_OFFSETD_CON0, CTRL_RESYNC);
}
void ddr_eye_print_termination_info(struct ab_ddr_context *ddr_ctx)
{
unsigned int zq_con0, zq_con3, zq_con6;
unsigned int dphy_mdll_con0, dphy_mdll_con1;
unsigned int dphy2_mdll_con0, dphy2_mdll_con1;
zq_con0 = ddr_reg_rd(ddr_ctx, DPHY_ZQ_CON0);
zq_con3 = ddr_reg_rd(ddr_ctx, DPHY_ZQ_CON3);
zq_con6 = ddr_reg_rd(ddr_ctx, DPHY_ZQ_CON6);
pr_info("----------------------------------------------------------\n");
pr_info("DataSlice_0 = DQ[7:0], DataSlice_1 = DQ[15:8]\n");
pr_info("ControlSlice = CA[5:0]\n");
pr_info("----------------------------------------------------------\n");
pr_info("ODT Information for DPHY\n");
pr_info(" 3'b100 : 60 Ohm Far end VSSQ termination\n");
pr_info(" 3'b010 : 120 Ohm Far end VSSQ termination\n");
pr_info(" 3'b001 : 240 Ohm Far end VSSQ termination\n");
pr_info("----------------------------------------------------------\n");
pr_info("ODT Data Slice 0: ZQ_CON6[5:3](zq_ds0_term) 0x%x\n",
(zq_con6 >> 3) & 0x7);
pr_info("ODT Data Slice 0: ZQ_CON6[13:11](zq_ds1_term) 0x%x\n",
(zq_con6 >> 11) & 0x7);
pr_info("----------------------------------------------------------\n");
pr_info("----------------------------------------------------------\n");
pr_info("Driver Strength Info for DPHY (Impedance output driver)\n");
pr_info(" 3'b100 : 48 Ohm, 3'b101 : 40 Ohm\n");
pr_info(" 3'b110 : 34 Ohm, 3'b111 : 30 Ohm\n");
pr_info("----------------------------------------------------------\n");
pr_info("Pull-Down Data Slice 0: ZQ_CON3[2:0](zq_ds0_pdds) 0x%x\n",
(zq_con3 >> 0) & 0x7);
pr_info("Pull-Down Data Slice 1: ZQ_CON3[10:8](zq_ds1_pdds) 0x%x\n",
(zq_con3 >> 8) & 0x7);
pr_info("Pull-Down Control Slice: ZQ_CON0[30:28](zq_mode_pdds) 0x%x\n",
(zq_con0 >> 28) & 0x7);
pr_info("Pull-Up Data Slice 0: ZQ_CON3[5:3](zq_ds0_dds) 0x%x\n",
(zq_con3 >> 3) & 0x7);
pr_info("Pull-Up Data Slice 1: ZQ_CON3[13:11](zq_ds1_dds) 0x%x\n",
(zq_con3 >> 11) & 0x7);
pr_info("Pull-Up Control Slice: ZQ_CON0[26:24](zq_mode_dds) 0x%x\n",
(zq_con0 >> 24) & 0x7);
pr_info("----------------------------------------------------------\n");
zq_con0 = ddr_reg_rd(ddr_ctx, DPHY2_ZQ_CON0);
zq_con3 = ddr_reg_rd(ddr_ctx, DPHY2_ZQ_CON3);
zq_con6 = ddr_reg_rd(ddr_ctx, DPHY2_ZQ_CON6);
pr_info("----------------------------------------------------------\n");
pr_info("ODT Information for DPHY2\n");
pr_info(" 3'b100 : 60 Ohm Far end VSSQ termination\n");
pr_info(" 3'b010 : 120 Ohm Far end VSSQ termination\n");
pr_info(" 3'b001 : 240 Ohm Far end VSSQ termination\n");
pr_info("----------------------------------------------------------\n");
pr_info("ODT Data Slice 0: ZQ_CON6[5:3](zq_ds0_term) 0x%x\n",
(zq_con6 >> 3) & 0x7);
pr_info("ODT Data Slice 0: ZQ_CON6[13:11](zq_ds1_term) 0x%x\n",
(zq_con6 >> 11) & 0x7);
pr_info("----------------------------------------------------------\n");
pr_info("----------------------------------------------------------\n");
pr_info("Driver Strength Info for DPHY2 (Impedance output driver)\n");
pr_info(" 3'b100 : 48 Ohm, 3'b101 : 40 Ohm\n");
pr_info(" 3'b110 : 34 Ohm, 3'b111 : 30 Ohm\n");
pr_info("----------------------------------------------------------\n");
pr_info("Pull-Down Data Slice 0: ZQ_CON3[2:0](zq_ds0_pdds) 0x%x\n",
(zq_con3 >> 0) & 0x7);
pr_info("Pull-Down Data Slice 1: ZQ_CON3[10:8](zq_ds1_pdds) 0x%x\n",
(zq_con3 >> 8) & 0x7);
pr_info("Pull-Down Control Slice: ZQ_CON0[30:28](zq_mode_pdds) 0x%x\n",
(zq_con0 >> 28) & 0x7);
pr_info("Pull-Up Data Slice 0: ZQ_CON3[5:3](zq_ds0_dds) 0x%x\n",
(zq_con3 >> 3) & 0x7);
pr_info("Pull-Up Data Slice 1: ZQ_CON3[13:11](zq_ds1_dds) 0x%x\n",
(zq_con3 >> 11) & 0x7);
pr_info("Pull-Up Control Slice: ZQ_CON0[26:24](zq_mode_dds) 0x%x\n",
(zq_con0 >> 24) & 0x7);
pr_info("----------------------------------------------------------\n");
pr_info("----------------------------------------------------------\n");
pr_info("DRAM ODT[MR11] and Drive-Strength[MR3] details:\n");
pr_info("----------------------------------------------------------\n");
pr_info("MR11: 0x%x, MR3: 0x%x\n", 0x24, 0xf1);
pr_info("----------------------------------------------------------\n");
dphy_mdll_con0 = ddr_reg_rd(ddr_ctx, DPHY_MDLL_CON0);
dphy_mdll_con1 = ddr_reg_rd(ddr_ctx, DPHY_MDLL_CON1);
dphy2_mdll_con0 = ddr_reg_rd(ddr_ctx, DPHY2_MDLL_CON0);
dphy2_mdll_con1 = ddr_reg_rd(ddr_ctx, DPHY2_MDLL_CON1);
pr_info("----------------------------------------------------------\n");
pr_info("DPHY_MDLL_CONx: [0x%x, 0x%x]\n",
dphy_mdll_con0, dphy_mdll_con1);
pr_info("ctrl_lock_value[9:0]: 0x%x, ctrl_locked: [%d]\n",
(dphy_mdll_con1 >> 8) & 0x3ff,
(dphy_mdll_con1 >> 18) & 0x1);
pr_info("DPHY2_MDLL_CONx: [0x%x, 0x%x]\n",
dphy2_mdll_con0, dphy2_mdll_con1);
pr_info("ctrl_lock_value[9:0]: 0x%x, ctrl_locked: [%d]\n",
(dphy2_mdll_con1 >> 8) & 0x3ff,
(dphy2_mdll_con1 >> 18) & 0x1);
pr_info("----------------------------------------------------------\n");
}
void ab_ddr_eye_margin_plot_read(struct ab_ddr_context *ddr_ctx)
{
char (*read_eye)[MAX_RW_OFFSETS];
ktime_t (*eye_time)[MAX_RW_OFFSETS];
int vrefIdx, offsetIdx, maxOffset;
read_eye = &ddr_ctx->eye_data->read_eye[0];
eye_time = &ddr_ctx->eye_data->read_eye_time[0];
maxOffset = ddr_ctx->eye_data->num_samples_read;
/* plot the pass fail information for each vref & delay offset */
for (vrefIdx = VREF_FROM; vrefIdx < PHY_VREF_LEVELS;
vrefIdx += VREF_STEP)
pr_info("VREF_0x%02x : %s\n", ddr_get_phy_vref(vrefIdx),
read_eye[vrefIdx]);
/* plot the time tracking information for read & compare */
for (vrefIdx = VREF_FROM; vrefIdx < PHY_VREF_LEVELS;
vrefIdx += VREF_STEP) {
pr_cont("VREF_0x%02x : ", ddr_get_phy_vref(vrefIdx));
for (offsetIdx = 0; offsetIdx < maxOffset; offsetIdx++)
pr_cont("%10llu ",
ktime_to_us(eye_time[vrefIdx][offsetIdx]));
pr_cont("\n");
}
}
void ab_ddr_eye_margin_plot_write(struct ab_ddr_context *ddr_ctx)
{
char (*write_eye)[MAX_RW_OFFSETS];
ktime_t (*eye_time)[MAX_RW_OFFSETS];
int vrefIdx, offsetIdx, maxOffset;
write_eye = &ddr_ctx->eye_data->write_eye[0];
eye_time = &ddr_ctx->eye_data->write_eye_time[0];
maxOffset = ddr_ctx->eye_data->num_samples_write;
/* plot the pass fail information for each vref & delay offset */
for (vrefIdx = VREF_FROM; vrefIdx < DRAM_VREF_LEVELS;
vrefIdx += VREF_STEP)
pr_info("VREF_0x%02x : %s\n", ddr_get_dram_vref(vrefIdx),
write_eye[vrefIdx]);
/* plot the time tracking information for read & compare */
for (vrefIdx = VREF_FROM; vrefIdx < DRAM_VREF_LEVELS;
vrefIdx += VREF_STEP) {
pr_cont("VREF_0x%02x : ", ddr_get_dram_vref(vrefIdx));
for (offsetIdx = 0; offsetIdx < maxOffset; offsetIdx++)
pr_cont("%10llu ",
ktime_to_us(eye_time[vrefIdx][offsetIdx]));
pr_cont("\n");
}
}
/* Caller must hold ddr_ctx->ddr_lock */
static void ddrphy_margin_eye_read(struct ab_ddr_context *ddr_ctx,
int samples, uint32_t eye_data)
{
int vrefIdx;
int maxOffset;
int offsetIdx, result_idx;
char (*read_eye)[MAX_RW_OFFSETS];
read_eye = &ddr_ctx->eye_data->read_eye[0];
maxOffset = samples;
for (vrefIdx = VREF_FROM; vrefIdx < PHY_VREF_LEVELS;
vrefIdx += VREF_STEP) {
ddrphy_set_read_vref(ddr_ctx, ddr_get_phy_vref(vrefIdx),
ddr_get_phy_vref(vrefIdx), VREF_BYTE_ALL);
pr_info("SET READ_VREF: 0x%02x\n", ddr_get_phy_vref(vrefIdx));
result_idx = 0;
for (offsetIdx = (maxOffset * -1); offsetIdx <= maxOffset;
offsetIdx++) {
ddrphy_set_read_offset(ddr_ctx, offsetIdx);
if (!__ab_ddr_read_write_test(ddr_ctx, eye_data))
read_eye[vrefIdx][result_idx] = 'o';
else
read_eye[vrefIdx][result_idx] = '.';
ddr_ctx->eye_data->read_eye_time[vrefIdx][result_idx] =
ktime_sub(ddr_ctx->et_read, ddr_ctx->st_read);
if (offsetIdx == 0)
read_eye[vrefIdx][result_idx] = '|';
result_idx++;
}
ddr_ctx->eye_data->num_samples_read = result_idx;
read_eye[vrefIdx][result_idx] = '\0';
}
ab_ddr_eye_margin_plot_read(ddr_ctx);
}
/* Caller must hold ddr_ctx->ddr_lock */
static void ddrphy_margin_eye_write(struct ab_ddr_context *ddr_ctx,
int samples, uint32_t eye_data)
{
int vrefIdx;
int maxOffset;
int offsetIdx, result_idx;
char (*write_eye)[MAX_RW_OFFSETS];
write_eye = &ddr_ctx->eye_data->write_eye[0];
maxOffset = samples;
for (vrefIdx = VREF_FROM; vrefIdx < DRAM_VREF_LEVELS;
vrefIdx += VREF_STEP) {
ddrphy_set_write_vref(ddr_ctx, ddr_get_dram_vref(vrefIdx),
VREF_BYTE_ALL);
pr_info("SET WRITE_VREF: 0x%02x\n", ddr_get_dram_vref(vrefIdx));
result_idx = 0;
for (offsetIdx = (maxOffset * -1); offsetIdx <= maxOffset;
offsetIdx++) {
ddrphy_set_write_offset(ddr_ctx, offsetIdx);
if (!__ab_ddr_read_write_test(ddr_ctx, eye_data))
write_eye[vrefIdx][result_idx] = 'o';
else
write_eye[vrefIdx][result_idx] = '.';
ddr_ctx->eye_data->write_eye_time[vrefIdx][result_idx] =
ktime_sub(ddr_ctx->et_read, ddr_ctx->st_read);
if (offsetIdx == 0)
write_eye[vrefIdx][result_idx] = '|';
result_idx++;
}
ddr_ctx->eye_data->num_samples_write = result_idx;
write_eye[vrefIdx][result_idx] = '\0';
}
ab_ddr_eye_margin_plot_write(ddr_ctx);
}
int ab_ddr_eye_margin_plot(void *ctx)
{
struct ab_ddr_context *ddr_ctx = (struct ab_ddr_context *)ctx;
if (!ddr_ctx->is_setup_done) {
pr_err("ddr eye margin: error!! ddr setup is not called\n");
return -EAGAIN;
}
if (!ddr_ctx->eye_data) {
pr_err("ddr eye margin: initial memory alloc failed\n");
return -ENOMEM;
}
if (!(ddr_ctx->eye_data->num_samples_write &&
ddr_ctx->eye_data->num_samples_read)) {
pr_err("ddr eye margin: error!! measure the eye before plot\n");
return DDR_FAIL;
}
ddr_eye_print_termination_info(ddr_ctx);
ab_ddr_eye_margin_plot_read(ddr_ctx);
ab_ddr_eye_margin_plot_write(ddr_ctx);
return 0;
}
int ab_ddr_eye_margin(void *ctx, unsigned int data)
{
uint32_t read_vref_phy0, read_vref_phy1;
uint32_t write_vref;
uint32_t num_samples;
uint32_t eye_data;
struct ab_ddr_context *ddr_ctx = (struct ab_ddr_context *)ctx;
if (!ddr_ctx->is_setup_done) {
pr_err("[ddr eye margin] Error!! ddr setup is not called\n");
return -EAGAIN;
}
/* Allow the eye margin test only when ddr state is DDR_ON */
if (ddr_ctx->ddr_state != DDR_ON) {
pr_err("ddr_eye_margin: Invalid ddr state: %d\n",
ddr_ctx->ddr_state);
return DDR_FAIL;
}
if (!ddr_ctx->eye_data) {
pr_err("ddr eye margin: initial memory alloc failed\n");
return -ENOMEM;
}
mutex_lock(&ddr_ctx->ddr_lock);
/* Read the MR14 register to get the VREF(DQ) information.
* Read the information while DDR is in self-refresh mode.
*/
write_vref = ddr_read_mr_reg(ddr_ctx, 14) & 0x3f;
ddr_eye_print_termination_info(ddr_ctx);
/* Disable the PHY control logic clock gating for s/w margin
* offset settings to work
*/
ddr_reg_clr(ddr_ctx, DPHY_LP_CON0, PCL_PD);
ddr_reg_clr(ddr_ctx, DPHY2_LP_CON0, PCL_PD);
read_vref_phy0 = ddr_reg_rd(ddr_ctx, DPHY_ZQ_CON9) & 0x3f;
read_vref_phy1 = ddr_reg_rd(ddr_ctx, DPHY2_ZQ_CON9) & 0x3f;
/* "data" provides the information about the type of test to be run
* (memtester/pcie_dma) for checking ddr data integrity
*/
eye_data = data;
num_samples = DDR_TEST_EYE_MARGIN_SAMPLES(eye_data);
if (num_samples < 50)
num_samples = 50;
pr_info("current read vref: 0x%02x, write vref: 0x%02x\n",
(read_vref_phy0 + read_vref_phy1) / 2, write_vref);
pr_info("RD_EYE: sweeping vref [0x%02x -> 0x%02x]\n",
ddr_get_phy_vref(VREF_FROM),
ddr_get_phy_vref(PHY_VREF_LEVELS - 1));
/* sweep read vref to get the eye margin on READ */
ddrphy_margin_eye_read(ddr_ctx, num_samples, eye_data);
/* Restore the working Read Vref & Offset */
ddrphy_set_read_vref(ddr_ctx, read_vref_phy0,
read_vref_phy1, VREF_BYTE_ALL);
ddrphy_set_read_offset(ddr_ctx, 0);
pr_info("WR_EYE: sweeping vref 0x%02x -> 0x%02x\n",
ddr_get_dram_vref(VREF_FROM),
ddr_get_dram_vref(DRAM_VREF_LEVELS - 1));
/* sweep write vref to get the eye margin on WRITE */
ddrphy_margin_eye_write(ddr_ctx, num_samples, eye_data);
/* Restore the working write Vref & Offset */
ddrphy_set_write_vref(ddr_ctx, write_vref, VREF_BYTE_ALL);
ddrphy_set_write_offset(ddr_ctx, 0);
/* Enable the PHY control logic clock gating after eyemargin test */
ddr_reg_set(ddr_ctx, DPHY_LP_CON0, PCL_PD);
ddr_reg_set(ddr_ctx, DPHY2_LP_CON0, PCL_PD);
mutex_unlock(&ddr_ctx->ddr_lock);
return DDR_SUCCESS;
}