6db4831e98
Android 14
1290 lines
35 KiB
C
1290 lines
35 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2019 MediaTek Inc.
|
|
* Author: Sagy Shih <sagy.shih@mediatek.com>
|
|
*/
|
|
#include <linux/bitops.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/printk.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/io.h>
|
|
#include <linux/arm-smccc.h>
|
|
#include <linux/soc/mediatek/mtk_sip_svc.h>
|
|
#include <memory/mediatek/emi.h>
|
|
|
|
/*
|
|
* EMI address-to-dram setting's structure.
|
|
* a2d is the abbreviation of addr2dram.
|
|
* s6s is the abbreviation of settings.
|
|
*/
|
|
struct a2d_s6s_v1 {
|
|
unsigned int magics[8];
|
|
unsigned long cas;
|
|
unsigned long chab_rk0_sz, chab_rk1_sz;
|
|
unsigned long chcd_rk0_sz, chcd_rk1_sz;
|
|
unsigned int channels;
|
|
unsigned int dualrk_ch0, dualrk_ch1;
|
|
unsigned int chn_hash_lsb, chnpos;
|
|
unsigned int chab_row_mask[2], chcd_row_mask[2];
|
|
unsigned int chab_col_mask[2], chcd_col_mask[2];
|
|
unsigned int dw32;
|
|
unsigned int chn_4bank_mode;
|
|
};
|
|
|
|
struct a2d_s6s_v2 {
|
|
unsigned int chn_bit_position;
|
|
unsigned int chn_en;
|
|
unsigned int magics[8];
|
|
|
|
unsigned int dual_rank_en;
|
|
unsigned int dw32_en;
|
|
unsigned int bg1_bk3_pos;
|
|
unsigned int rank_pos;
|
|
unsigned int magics2[7];
|
|
unsigned int rank0_row_width, rank0_bank_width, rank0_col_width;
|
|
unsigned int rank0_size_MB, rank0_bg_16bank_mode;
|
|
unsigned int rank1_row_width, rank1_bank_width, rank1_col_width;
|
|
unsigned int rank1_size_MB, rank1_bg_16bank_mode;
|
|
};
|
|
|
|
struct emi_cen {
|
|
/*
|
|
* EMI setting from device tree
|
|
*/
|
|
int ver;
|
|
unsigned int emi_cen_cnt;
|
|
unsigned int ch_cnt;
|
|
unsigned int rk_cnt;
|
|
unsigned long long *rk_size;
|
|
void __iomem **emi_cen_base;
|
|
void __iomem **emi_chn_base;
|
|
|
|
/* address from the sysfs file for EMI addr2dram */
|
|
unsigned long a2d_addr;
|
|
|
|
/*
|
|
* EMI addr2dram settings from device tree
|
|
*/
|
|
unsigned int disph;
|
|
unsigned int hash;
|
|
|
|
/*
|
|
* EMI addr2dram settings calculated at run time
|
|
*/
|
|
unsigned long offset;
|
|
unsigned long max;
|
|
union {
|
|
struct a2d_s6s_v1 v1;
|
|
struct a2d_s6s_v2 v2;
|
|
} a2d_s6s;
|
|
};
|
|
|
|
#define EMI_CONA_DW32_EN 1
|
|
#define EMI_CONA_CHN_POS_0 2
|
|
#define EMI_CONA_CHN_POS_1 3
|
|
#define EMI_CONA_COL 4
|
|
#define EMI_CONA_COL2ND 6
|
|
#define EMI_CONA_CHN_EN 8
|
|
#define EMI_CONA_ROW 12
|
|
#define EMI_CONA_ROW2ND 14
|
|
#define EMI_CONA_DUAL_RANK_EN_CHN1 16
|
|
#define EMI_CONA_DUAL_RANK_EN 17
|
|
#define EMI_CONA_CAS_SIZE 18
|
|
#define EMI_CONA_CHN1_COL 20
|
|
#define EMI_CONA_CHN1_COL2ND 22
|
|
#define EMI_CONA_ROW_EXT0 24
|
|
#define EMI_CONA_ROW2ND_EXT0 25
|
|
#define EMI_CONA_CAS_SIZE_BIT3 26
|
|
#define EMI_CONA_RANK_POS 27
|
|
#define EMI_CONA_CHN1_ROW 28
|
|
#define EMI_CONA_CHN1_ROW2ND 30
|
|
|
|
#define EMI_CONH_CHN1_ROW_EXT0 4
|
|
#define EMI_CONH_CHN1_ROW2ND_EXT0 5
|
|
#define EMI_CONH_CHNAB_RANK0_SIZE 16
|
|
#define EMI_CONH_CHNAB_RANK1_SIZE 20
|
|
#define EMI_CONH_CHNCD_RANK0_SIZE 24
|
|
#define EMI_CONH_CHNCD_RANK1_SIZE 28
|
|
|
|
#define EMI_CONH_2ND_CHN_4BANK_MODE 6
|
|
|
|
#define EMI_CONK_CHNAB_RANK0_SIZE_EXT 16
|
|
#define EMI_CONK_CHNAB_RANK1_SIZE_EXT 20
|
|
#define EMI_CONK_CHNCD_RANK0_SIZE_EXT 24
|
|
#define EMI_CONK_CHNCD_RANK1_SIZE_EXT 28
|
|
|
|
#define EMI_CHN_CONA_DUAL_RANK_EN 0
|
|
#define EMI_CHN_CONA_DW32_EN 1
|
|
#define EMI_CHN_CONA_ROW_EXT0 2
|
|
#define EMI_CHN_CONA_ROW2ND_EXT0 3
|
|
#define EMI_CHN_CONA_COL 4
|
|
#define EMI_CHN_CONA_COL2ND 6
|
|
#define EMI_CHN_CONA_RANK0_SIZE_EXT 8
|
|
#define EMI_CHN_CONA_RANK1_SIZE_EXT 9
|
|
#define EMI_CHN_CONA_16BANK_MODE 10
|
|
#define EMI_CHN_CONA_16BANK_MODE_2ND 11
|
|
#define EMI_CHN_CONA_ROW 12
|
|
#define EMI_CHN_CONA_ROW2ND 14
|
|
#define EMI_CHN_CONA_RANK0_SZ 16
|
|
#define EMI_CHN_CONA_RANK1_SZ 20
|
|
#define EMI_CHN_CONA_4BANK_MODE 24
|
|
#define EMI_CHN_CONA_4BANK_MODE_2ND 25
|
|
#define EMI_CHN_CONA_RANK_POS 27
|
|
#define EMI_CHN_CONA_BG1_BK3_POS 31
|
|
|
|
#define MTK_EMI_DRAM_OFFSET 0x40000000
|
|
#define MTK_EMI_HASH 0x7
|
|
#define MTK_EMI_DISPATCH 0x0
|
|
#define MTK_EMI_A2D_VERSION 1
|
|
|
|
static unsigned int emi_a2d_con_offset[] = {
|
|
/* central EMI CONA, CONF, CONH, CONH_2ND, CONK */
|
|
0x00, 0x28, 0x38, 0x3c, 0x50,
|
|
};
|
|
|
|
static unsigned int emi_a2d_chn_con_offset[] = {
|
|
/* channel EMI CONA, CONC, CONC_2ND */
|
|
0x00, 0x10, 0x14
|
|
};
|
|
|
|
/* global pointer for exported functions */
|
|
static struct emi_cen *global_emi_cen;
|
|
|
|
DEFINE_SPINLOCK(emidbg_lock);
|
|
|
|
/*
|
|
* prepare_a2d_v1: a helper function to initialize and calculate settings for
|
|
* the mtk_emicen_addr2dram_v1() function
|
|
*
|
|
* There is no code comment for the translation. This is intended since
|
|
* the fomular of translation is derived from the implementation of EMI.
|
|
*/
|
|
static inline void prepare_a2d_v1(struct emi_cen *cen)
|
|
{
|
|
const unsigned int mask_4b = 0xf, mask_2b = 0x3;
|
|
void __iomem *emi_cen_base;
|
|
struct a2d_s6s_v1 *s6s;
|
|
unsigned long emi_cona;
|
|
unsigned long emi_conf;
|
|
unsigned long emi_conh;
|
|
unsigned long emi_conh_2nd;
|
|
unsigned long emi_conk;
|
|
unsigned long tmp;
|
|
|
|
if (!cen)
|
|
return;
|
|
|
|
emi_cen_base = cen->emi_cen_base[0];
|
|
s6s = &cen->a2d_s6s.v1;
|
|
|
|
emi_cona = readl(emi_cen_base + emi_a2d_con_offset[0]);
|
|
emi_conf = readl(emi_cen_base + emi_a2d_con_offset[1]);
|
|
emi_conh = readl(emi_cen_base + emi_a2d_con_offset[2]);
|
|
emi_conh_2nd = readl(emi_cen_base + emi_a2d_con_offset[3]);
|
|
emi_conk = readl(emi_cen_base + emi_a2d_con_offset[4]);
|
|
|
|
cen->offset = MTK_EMI_DRAM_OFFSET;
|
|
|
|
s6s->magics[0] = emi_conf & mask_4b;
|
|
s6s->magics[1] = (emi_conf >> 4) & mask_4b;
|
|
s6s->magics[2] = (emi_conf >> 8) & mask_4b;
|
|
s6s->magics[3] = (emi_conf >> 12) & mask_4b;
|
|
s6s->magics[4] = (emi_conf >> 16) & mask_4b;
|
|
s6s->magics[5] = (emi_conf >> 20) & mask_4b;
|
|
s6s->magics[6] = (emi_conf >> 24) & mask_4b;
|
|
s6s->magics[7] = (emi_conf >> 28) & mask_4b;
|
|
|
|
s6s->dw32 = test_bit(EMI_CONA_DW32_EN, &emi_cona) ? 1 : 0;
|
|
|
|
s6s->channels = (emi_cona >> EMI_CONA_CHN_EN) & mask_2b;
|
|
|
|
s6s->cas = (emi_cona >> EMI_CONA_CAS_SIZE) & mask_2b;
|
|
s6s->cas += s6s->dw32 << 2;
|
|
s6s->cas += ((emi_cona >> EMI_CONA_CAS_SIZE_BIT3) & 1) << 3;
|
|
s6s->cas = s6s->cas << 28;
|
|
s6s->cas = s6s->cas << s6s->channels;
|
|
|
|
s6s->dualrk_ch0 = test_bit(EMI_CONA_DUAL_RANK_EN, &emi_cona) ? 1 : 0;
|
|
s6s->dualrk_ch1 = test_bit(EMI_CONA_DUAL_RANK_EN_CHN1, &emi_cona) ? 1 : 0;
|
|
|
|
s6s->chn_hash_lsb = 7 + (cen->hash & (~(cen->hash) + 1));
|
|
if (cen->hash)
|
|
s6s->chnpos = s6s->chn_hash_lsb;
|
|
else {
|
|
s6s->chnpos = test_bit(EMI_CONA_CHN_POS_1, &emi_cona) ? 2 : 0;
|
|
s6s->chnpos |= test_bit(EMI_CONA_CHN_POS_0, &emi_cona) ? 1 : 0;
|
|
}
|
|
|
|
tmp = (emi_conh >> EMI_CONH_CHNAB_RANK0_SIZE) & mask_4b;
|
|
tmp += ((emi_conk >> EMI_CONK_CHNAB_RANK0_SIZE_EXT) & mask_4b) << 4;
|
|
if (tmp)
|
|
s6s->chab_rk0_sz = tmp << 8;
|
|
else {
|
|
tmp = (emi_cona >> EMI_CONA_COL) & mask_2b;
|
|
tmp += (emi_cona >> EMI_CONA_ROW) & mask_2b;
|
|
tmp += test_bit(EMI_CONA_ROW_EXT0, &emi_cona) ? 4 : 0;
|
|
tmp += s6s->dw32;
|
|
tmp += 7;
|
|
s6s->chab_rk0_sz = 1 << tmp;
|
|
}
|
|
|
|
tmp = (emi_conh >> EMI_CONH_CHNAB_RANK1_SIZE) & mask_4b;
|
|
tmp += ((emi_conk >> EMI_CONK_CHNAB_RANK1_SIZE_EXT) & mask_4b) << 4;
|
|
if (tmp)
|
|
s6s->chab_rk1_sz = tmp << 8;
|
|
else if (!test_bit(EMI_CONA_DUAL_RANK_EN, &emi_cona))
|
|
s6s->chab_rk1_sz = 0;
|
|
else {
|
|
tmp = (emi_cona >> EMI_CONA_COL2ND) & mask_2b;
|
|
tmp += (emi_cona >> EMI_CONA_ROW2ND) & mask_2b;
|
|
tmp += test_bit(EMI_CONA_ROW2ND_EXT0, &emi_cona) ? 4 : 0;
|
|
tmp += s6s->dw32;
|
|
tmp += 7;
|
|
s6s->chab_rk1_sz = 1 << tmp;
|
|
}
|
|
|
|
tmp = (emi_conh >> EMI_CONH_CHNCD_RANK0_SIZE) & mask_4b;
|
|
tmp += ((emi_conk >> EMI_CONK_CHNCD_RANK0_SIZE_EXT) & mask_4b) << 4;
|
|
if (tmp)
|
|
s6s->chcd_rk0_sz = tmp << 8;
|
|
else {
|
|
tmp = (emi_cona >> EMI_CONA_CHN1_COL) & mask_2b;
|
|
tmp += (emi_cona >> EMI_CONA_CHN1_ROW) & mask_2b;
|
|
tmp += test_bit(EMI_CONH_CHN1_ROW_EXT0, &emi_conh) ? 4 : 0;
|
|
tmp += s6s->dw32;
|
|
tmp += 7;
|
|
s6s->chcd_rk0_sz = 1 << tmp;
|
|
}
|
|
|
|
tmp = (emi_conh >> EMI_CONH_CHNCD_RANK1_SIZE) & mask_4b;
|
|
tmp += ((emi_conk >> EMI_CONK_CHNCD_RANK1_SIZE_EXT) & mask_4b) << 4;
|
|
if (tmp)
|
|
s6s->chcd_rk1_sz = tmp << 8;
|
|
else if (!test_bit(EMI_CONA_DUAL_RANK_EN_CHN1, &emi_cona))
|
|
s6s->chcd_rk1_sz = 0;
|
|
else {
|
|
tmp = (emi_cona >> EMI_CONA_CHN1_COL2ND) & mask_2b;
|
|
tmp += (emi_cona >> EMI_CONA_CHN1_ROW2ND) & mask_2b;
|
|
tmp += test_bit(EMI_CONH_CHN1_ROW2ND_EXT0, &emi_conh) ? 4 : 0;
|
|
tmp += s6s->dw32;
|
|
tmp += 7;
|
|
s6s->chcd_rk1_sz = 1 << tmp;
|
|
}
|
|
|
|
cen->max = s6s->chab_rk0_sz + s6s->chab_rk1_sz;
|
|
cen->max += s6s->chcd_rk0_sz + s6s->chcd_rk0_sz;
|
|
if ((s6s->channels > 1) || (cen->disph > 0))
|
|
cen->max *= 2;
|
|
cen->max = cen->max << 20;
|
|
|
|
s6s->chab_row_mask[0] = (emi_cona >> EMI_CONA_ROW) & mask_2b;
|
|
s6s->chab_row_mask[0] += test_bit(EMI_CONA_ROW_EXT0, &emi_cona) ? 4 : 0;
|
|
s6s->chab_row_mask[0] += 13;
|
|
s6s->chab_row_mask[1] = (emi_cona >> EMI_CONA_ROW2ND) & mask_2b;
|
|
s6s->chab_row_mask[1] += test_bit(EMI_CONA_ROW2ND_EXT0, &emi_cona) ? 4 : 0;
|
|
s6s->chab_row_mask[1] += 13;
|
|
|
|
s6s->chcd_row_mask[0] = (emi_cona >> EMI_CONA_CHN1_ROW) & mask_2b;
|
|
s6s->chcd_row_mask[0] += test_bit(EMI_CONH_CHN1_ROW_EXT0, &emi_conh) ? 4 : 0;
|
|
s6s->chcd_row_mask[0] += 13;
|
|
s6s->chcd_row_mask[1] = (emi_cona >> EMI_CONA_CHN1_ROW2ND) & mask_2b;
|
|
s6s->chcd_row_mask[1] += test_bit(EMI_CONH_CHN1_ROW2ND_EXT0, &emi_conh) ? 4 : 0;
|
|
s6s->chcd_row_mask[1] += 13;
|
|
|
|
s6s->chab_col_mask[0] = (emi_cona >> EMI_CONA_COL) & mask_2b;
|
|
s6s->chab_col_mask[0] += 9;
|
|
s6s->chab_col_mask[1] = (emi_cona >> EMI_CONA_COL2ND) & mask_2b;
|
|
s6s->chab_col_mask[1] += 9;
|
|
|
|
s6s->chcd_col_mask[0] = (emi_cona >> EMI_CONA_CHN1_COL) & mask_2b;
|
|
s6s->chcd_col_mask[0] += 9;
|
|
s6s->chcd_col_mask[1] = (emi_cona >> EMI_CONA_CHN1_COL2ND) & mask_2b;
|
|
s6s->chcd_col_mask[1] += 9;
|
|
|
|
s6s->chn_4bank_mode = test_bit(EMI_CONH_2ND_CHN_4BANK_MODE, &emi_conh_2nd) ? 1 : 0;
|
|
}
|
|
|
|
/*
|
|
* use_a2d_magic_v1: a helper function to calculate the input address
|
|
* for the mtk_emicen_addr2dram_v1() function
|
|
*
|
|
* There is no code comment for the translation. This is intended since
|
|
* the fomular of translation is derived from the implementation of EMI.
|
|
*/
|
|
static inline unsigned int use_a2d_magic_v1(unsigned long addr,
|
|
unsigned int bit)
|
|
{
|
|
unsigned long magic;
|
|
unsigned int ret;
|
|
|
|
if (!global_emi_cen)
|
|
return 0;
|
|
|
|
magic = global_emi_cen->a2d_s6s.v1.magics[((bit >= 9) & (bit <= 16)) ?
|
|
(bit - 9) : 0];
|
|
|
|
ret = test_bit(bit, &addr) ? 1 : 0;
|
|
ret ^= (test_bit(16, &addr) && test_bit(0, &magic)) ? 1 : 0;
|
|
ret ^= (test_bit(17, &addr) && test_bit(1, &magic)) ? 1 : 0;
|
|
ret ^= (test_bit(18, &addr) && test_bit(2, &magic)) ? 1 : 0;
|
|
ret ^= (test_bit(19, &addr) && test_bit(3, &magic)) ? 1 : 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* mtk_emicen_addr2dram_v1 - Translate a physical address to
|
|
* a DRAM-point-of-view map for EMI v1
|
|
* @addr - input physical address
|
|
* @map - output map stored in struct emi_addr_map
|
|
*
|
|
* Return 0 on success, -1 on failures.
|
|
*
|
|
* There is no code comment for the translation. This is intended since
|
|
* the fomular of translation is derived from the implementation of EMI.
|
|
*/
|
|
static int mtk_emicen_addr2dram_v1(unsigned long addr,
|
|
struct emi_addr_map *map)
|
|
{
|
|
struct a2d_s6s_v1 *s6s;
|
|
unsigned long disph, hash;
|
|
unsigned long saddr, bfraddr, chnaddr;
|
|
unsigned long max_rk0_sz;
|
|
unsigned int tmp;
|
|
unsigned int chn_hash_lsb, row_mask, col_mask;
|
|
bool ch_ab_not_cd;
|
|
|
|
if (!global_emi_cen)
|
|
return -1;
|
|
if (!map)
|
|
return -1;
|
|
if (addr < global_emi_cen->offset)
|
|
return -1;
|
|
if (addr > global_emi_cen->max)
|
|
return -1;
|
|
|
|
addr -= global_emi_cen->offset;
|
|
|
|
map->emi = -1;
|
|
map->channel = -1;
|
|
map->rank = -1;
|
|
map->bank = -1;
|
|
map->row = -1;
|
|
map->column = -1;
|
|
|
|
s6s = &global_emi_cen->a2d_s6s.v1;
|
|
disph = global_emi_cen->disph;
|
|
hash = global_emi_cen->hash;
|
|
chn_hash_lsb = s6s->chn_hash_lsb;
|
|
|
|
tmp = (test_bit(8, &addr) & test_bit(0, &disph)) ? 1 : 0;
|
|
tmp ^= (test_bit(9, &addr) & test_bit(1, &disph)) ? 1 : 0;
|
|
tmp ^= (test_bit(10, &addr) & test_bit(2, &disph)) ? 1 : 0;
|
|
tmp ^= (test_bit(11, &addr) & test_bit(3, &disph)) ? 1 : 0;
|
|
map->emi = tmp;
|
|
|
|
saddr = addr;
|
|
clear_bit(9, &saddr);
|
|
clear_bit(10, &saddr);
|
|
clear_bit(11, &saddr);
|
|
clear_bit(12, &saddr);
|
|
clear_bit(13, &saddr);
|
|
clear_bit(14, &saddr);
|
|
clear_bit(15, &saddr);
|
|
clear_bit(16, &saddr);
|
|
saddr |= use_a2d_magic_v1(addr, 9) << 9;
|
|
saddr |= use_a2d_magic_v1(addr, 10) << 10;
|
|
saddr |= use_a2d_magic_v1(addr, 11) << 11;
|
|
saddr |= use_a2d_magic_v1(addr, 12) << 12;
|
|
saddr |= use_a2d_magic_v1(addr, 13) << 13;
|
|
saddr |= use_a2d_magic_v1(addr, 14) << 14;
|
|
saddr |= use_a2d_magic_v1(addr, 15) << 15;
|
|
saddr |= use_a2d_magic_v1(addr, 16) << 16;
|
|
|
|
if (global_emi_cen->disph <= 0)
|
|
bfraddr = saddr;
|
|
else {
|
|
tmp = 7 + __ffs(disph);
|
|
bfraddr = (saddr >> (tmp + 1)) << tmp;
|
|
bfraddr += saddr & ((1 << tmp) - 1);
|
|
}
|
|
|
|
if (bfraddr < s6s->cas)
|
|
return -1;
|
|
|
|
if (!s6s->channels)
|
|
map->channel = s6s->channels;
|
|
else if (hash) {
|
|
tmp = (test_bit(8, &addr) && test_bit(0, &hash)) ? 1 : 0;
|
|
tmp ^= (test_bit(9, &addr) && test_bit(1, &hash)) ? 1 : 0;
|
|
tmp ^= (test_bit(10, &addr) && test_bit(2, &hash)) ? 1 : 0;
|
|
tmp ^= (test_bit(11, &addr) && test_bit(3, &hash)) ? 1 : 0;
|
|
map->channel = tmp;
|
|
} else {
|
|
if (s6s->channels == 1) {
|
|
tmp = 0;
|
|
switch (s6s->chnpos) {
|
|
case 0:
|
|
tmp = 7;
|
|
break;
|
|
case 1:
|
|
tmp = 8;
|
|
break;
|
|
case 2:
|
|
tmp = 9;
|
|
break;
|
|
case 3:
|
|
tmp = 12;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
map->channel = (bfraddr >> tmp) % 2;
|
|
} else if (s6s->channels == 2) {
|
|
tmp = 0;
|
|
switch (s6s->chnpos) {
|
|
case 0:
|
|
tmp = 7;
|
|
break;
|
|
case 1:
|
|
tmp = 8;
|
|
break;
|
|
case 2:
|
|
tmp = 9;
|
|
break;
|
|
case 3:
|
|
tmp = 12;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
map->channel = (bfraddr >> tmp) % 4;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (map->channel > 1)
|
|
ch_ab_not_cd = 0;
|
|
else {
|
|
if (map->channel == 1)
|
|
ch_ab_not_cd = (s6s->channels > 1) ? 1 : 0;
|
|
else
|
|
ch_ab_not_cd = 1;
|
|
}
|
|
|
|
max_rk0_sz = (ch_ab_not_cd) ? s6s->chab_rk0_sz : s6s->chcd_rk0_sz;
|
|
max_rk0_sz = max_rk0_sz << 20;
|
|
|
|
if (!s6s->channels)
|
|
chnaddr = bfraddr;
|
|
else if (s6s->chnpos > 3) {
|
|
tmp = chn_hash_lsb;
|
|
chnaddr = bfraddr >> (tmp + 1);
|
|
chnaddr = chnaddr << tmp;
|
|
chnaddr += bfraddr & ((1 << tmp) - 1);
|
|
} else if (s6s->channels == 1 ||
|
|
s6s->channels == 2) {
|
|
tmp = 0;
|
|
switch (s6s->chnpos) {
|
|
case 0:
|
|
tmp = 7;
|
|
break;
|
|
case 1:
|
|
tmp = 8;
|
|
break;
|
|
case 2:
|
|
tmp = 9;
|
|
break;
|
|
case 3:
|
|
tmp = 12;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
chnaddr = bfraddr >> (tmp + (s6s->channels - 1));
|
|
chnaddr = chnaddr << tmp;
|
|
chnaddr += bfraddr & ((1 << tmp) - 1);
|
|
} else {
|
|
return -1;
|
|
}
|
|
|
|
if ((map->channel) ? !s6s->dualrk_ch1 : !s6s->dualrk_ch0)
|
|
map->rank = 0;
|
|
else {
|
|
if (chnaddr > max_rk0_sz)
|
|
map->rank = 1;
|
|
else
|
|
map->rank = 0;
|
|
}
|
|
|
|
row_mask = (ch_ab_not_cd) ?
|
|
((map->rank) ?
|
|
s6s->chab_row_mask[1] : s6s->chab_row_mask[0]) :
|
|
((map->rank) ?
|
|
s6s->chcd_row_mask[1] : s6s->chcd_row_mask[0]);
|
|
col_mask = (ch_ab_not_cd) ?
|
|
((map->rank) ?
|
|
s6s->chab_col_mask[1] : s6s->chab_col_mask[0]) :
|
|
((map->rank) ?
|
|
s6s->chcd_col_mask[1] : s6s->chcd_col_mask[0]);
|
|
|
|
tmp = chnaddr - (max_rk0_sz * map->rank);
|
|
tmp /= 1 << (s6s->dw32 + 1 + col_mask + 3);
|
|
tmp &= (1 << row_mask) - 1;
|
|
map->row = tmp;
|
|
|
|
tmp = chnaddr;
|
|
tmp /= 1 << (s6s->dw32 + 1 + col_mask);
|
|
tmp &= ((!s6s->chn_4bank_mode) ? 8 : 4) - 1;
|
|
map->bank = tmp;
|
|
|
|
tmp = chnaddr;
|
|
tmp /= 1 << (s6s->dw32 + 1);
|
|
tmp &= (1 << col_mask) - 1;
|
|
map->column = tmp;
|
|
|
|
return 0;
|
|
}
|
|
/*
|
|
* prepare_a2d_v2: a helper function to initialize and calculate settings for
|
|
* the mtk_emicen_addr2dram_v2() function
|
|
*
|
|
* There is no code comment for the translation. This is intended since
|
|
* the fomular of translation is derived from the implementation of EMI.
|
|
*/
|
|
static inline void prepare_a2d_v2(struct emi_cen *cen)
|
|
{
|
|
const unsigned int mask_4b = 0xf, mask_2b = 0x3;
|
|
struct a2d_s6s_v2 *s6s;
|
|
void __iomem *emi_cen_base, *emi_chn_base;
|
|
unsigned long emi_cona, emi_conf, emi_conh, emi_conh_2nd, emi_conk;
|
|
unsigned long emi_chn_cona, emi_chn_conc, emi_chn_conc_2nd;
|
|
int tmp;
|
|
int col, col2nd, row, row2nd, row_ext0, row2nd_ext0;
|
|
int rank0_size, rank1_size, rank0_size_ext, rank1_size_ext;
|
|
int chn_4bank_mode, chn_bg_16bank_mode, chn_bg_16bank_mode_2nd;
|
|
int b11s, b12s, b13s, b14s, b15s, b16s;
|
|
int b8s, b11s_ext, b12s_ext, b13s_ext, b14s_ext, b15s_ext, b16s_ext;
|
|
unsigned long ch0_rk0_sz, ch0_rk1_sz;
|
|
unsigned long ch1_rk0_sz, ch1_rk1_sz;
|
|
|
|
if (!cen)
|
|
return;
|
|
|
|
s6s = &cen->a2d_s6s.v2;
|
|
cen->offset = MTK_EMI_DRAM_OFFSET;
|
|
|
|
emi_cen_base = cen->emi_cen_base[0];
|
|
emi_cona = readl(emi_cen_base + emi_a2d_con_offset[0]);
|
|
emi_conf = readl(emi_cen_base + emi_a2d_con_offset[1]);
|
|
emi_conh = readl(emi_cen_base + emi_a2d_con_offset[2]);
|
|
emi_conh_2nd = readl(emi_cen_base + emi_a2d_con_offset[3]);
|
|
emi_conk = readl(emi_cen_base + emi_a2d_con_offset[4]);
|
|
|
|
emi_chn_base = cen->emi_chn_base[0];
|
|
emi_chn_cona = readl(emi_chn_base + emi_a2d_chn_con_offset[0]);
|
|
emi_chn_conc = readl(emi_chn_base + emi_a2d_chn_con_offset[1]);
|
|
emi_chn_conc_2nd = readl(emi_chn_base + emi_a2d_chn_con_offset[2]);
|
|
|
|
tmp = (emi_cona >> EMI_CONA_CHN_POS_0) & mask_2b;
|
|
switch (tmp) {
|
|
case 3:
|
|
s6s->chn_bit_position = 12;
|
|
break;
|
|
case 2:
|
|
s6s->chn_bit_position = 9;
|
|
break;
|
|
case 1:
|
|
s6s->chn_bit_position = 8;
|
|
break;
|
|
default:
|
|
s6s->chn_bit_position = 7;
|
|
break;
|
|
}
|
|
|
|
s6s->chn_en = (emi_cona >> EMI_CONA_CHN_EN) & mask_2b;
|
|
|
|
s6s->magics[0] = emi_conf & mask_4b;
|
|
s6s->magics[1] = (emi_conf >> 4) & mask_4b;
|
|
s6s->magics[2] = (emi_conf >> 8) & mask_4b;
|
|
s6s->magics[3] = (emi_conf >> 12) & mask_4b;
|
|
s6s->magics[4] = (emi_conf >> 16) & mask_4b;
|
|
s6s->magics[5] = (emi_conf >> 20) & mask_4b;
|
|
s6s->magics[6] = (emi_conf >> 24) & mask_4b;
|
|
s6s->magics[7] = (emi_conf >> 28) & mask_4b;
|
|
|
|
|
|
s6s->dual_rank_en =
|
|
test_bit(EMI_CHN_CONA_DUAL_RANK_EN, &emi_chn_cona) ? 1 : 0;
|
|
s6s->dw32_en = test_bit(EMI_CHN_CONA_DW32_EN, &emi_chn_cona) ? 1 : 0;
|
|
row_ext0 = test_bit(EMI_CHN_CONA_ROW_EXT0, &emi_chn_cona) ? 1 : 0;
|
|
row2nd_ext0 = test_bit(EMI_CHN_CONA_ROW2ND_EXT0, &emi_chn_cona) ? 1 : 0;
|
|
col = (emi_chn_cona >> EMI_CHN_CONA_COL) & mask_2b;
|
|
col2nd = (emi_chn_cona >> EMI_CHN_CONA_COL2ND) & mask_2b;
|
|
rank0_size_ext =
|
|
test_bit(EMI_CHN_CONA_RANK0_SIZE_EXT, &emi_chn_cona) ? 1 : 0;
|
|
rank1_size_ext =
|
|
test_bit(EMI_CHN_CONA_RANK1_SIZE_EXT, &emi_chn_cona) ? 1 : 0;
|
|
chn_bg_16bank_mode =
|
|
test_bit(EMI_CHN_CONA_16BANK_MODE, &emi_chn_cona) ? 1 : 0;
|
|
chn_bg_16bank_mode_2nd =
|
|
test_bit(EMI_CHN_CONA_16BANK_MODE_2ND, &emi_chn_cona) ? 1 : 0;
|
|
row = (emi_chn_cona >> EMI_CHN_CONA_ROW) & mask_2b;
|
|
row2nd = (emi_chn_cona >> EMI_CHN_CONA_ROW2ND) & mask_2b;
|
|
rank0_size = (emi_chn_cona >> EMI_CHN_CONA_RANK0_SZ) & mask_4b;
|
|
rank1_size = (emi_chn_cona >> EMI_CHN_CONA_RANK1_SZ) & mask_4b;
|
|
chn_4bank_mode =
|
|
test_bit(EMI_CHN_CONA_4BANK_MODE, &emi_chn_cona) ? 1 : 0;
|
|
s6s->rank_pos = test_bit(EMI_CHN_CONA_RANK_POS, &emi_chn_cona) ? 1 : 0;
|
|
s6s->bg1_bk3_pos =
|
|
test_bit(EMI_CHN_CONA_BG1_BK3_POS, &emi_chn_cona) ? 1 : 0;
|
|
|
|
b11s = (emi_chn_conc >> 8) & mask_4b;
|
|
b12s = (emi_chn_conc >> 12) & mask_4b;
|
|
b13s = (emi_chn_conc >> 16) & mask_4b;
|
|
b14s = (emi_chn_conc >> 20) & mask_4b;
|
|
b15s = (emi_chn_conc >> 24) & mask_4b;
|
|
b16s = (emi_chn_conc >> 28) & mask_4b;
|
|
|
|
b11s_ext = (emi_chn_conc_2nd >> 4) & mask_2b;
|
|
b12s_ext = (emi_chn_conc_2nd >> 6) & mask_2b;
|
|
b13s_ext = (emi_chn_conc_2nd >> 8) & mask_2b;
|
|
b14s_ext = (emi_chn_conc_2nd >> 10) & mask_2b;
|
|
b15s_ext = (emi_chn_conc_2nd >> 12) & mask_2b;
|
|
b16s_ext = (emi_chn_conc_2nd >> 14) & mask_2b;
|
|
b8s = (emi_chn_conc_2nd >> 16) & mask_2b;
|
|
|
|
s6s->magics2[0] = b8s;
|
|
s6s->magics2[1] = b11s_ext * 16 + b11s;
|
|
s6s->magics2[2] = b12s_ext * 16 + b12s;
|
|
s6s->magics2[3] = b13s_ext * 16 + b13s;
|
|
s6s->magics2[4] = b14s_ext * 16 + b14s;
|
|
s6s->magics2[5] = b15s_ext * 16 + b15s;
|
|
s6s->magics2[6] = b16s_ext * 16 + b16s;
|
|
|
|
s6s->rank0_row_width = row_ext0 * 4 + row + 13;
|
|
s6s->rank0_bank_width = (chn_bg_16bank_mode == 1) ? 4 :
|
|
(chn_4bank_mode == 1) ? 2 : 3;
|
|
s6s->rank0_col_width = col + 9;
|
|
s6s->rank0_bg_16bank_mode = chn_bg_16bank_mode;
|
|
s6s->rank0_size_MB = (rank0_size_ext * 16 + rank0_size) * 256;
|
|
if (!(s6s->rank0_size_MB)) {
|
|
tmp = s6s->rank0_row_width + s6s->rank0_bank_width;
|
|
tmp += s6s->rank0_col_width + s6s->dw32_en;
|
|
s6s->rank0_size_MB = 2 << (tmp - 20);
|
|
}
|
|
|
|
s6s->rank1_row_width = row2nd_ext0 * 4 + row2nd + 13;
|
|
s6s->rank1_bank_width = (chn_bg_16bank_mode_2nd == 1) ? 4 :
|
|
(chn_4bank_mode == 1) ? 2 : 3;
|
|
s6s->rank1_col_width = col2nd + 9;
|
|
s6s->rank1_bg_16bank_mode = chn_bg_16bank_mode_2nd;
|
|
s6s->rank1_size_MB = (rank1_size_ext * 16 + rank1_size) * 256;
|
|
if (!(s6s->rank1_size_MB)) {
|
|
tmp = s6s->rank1_row_width + s6s->rank1_bank_width;
|
|
tmp += s6s->rank1_col_width + s6s->dw32_en;
|
|
s6s->rank1_size_MB = 2 << (tmp - 20);
|
|
}
|
|
if (s6s->rank0_size_MB)
|
|
ch0_rk0_sz = s6s->rank0_size_MB;
|
|
else {
|
|
tmp = s6s->rank0_row_width + s6s->rank0_bank_width;
|
|
tmp += s6s->rank0_col_width + s6s->dw32_en ? 2 : 1;
|
|
tmp -= 20;
|
|
ch0_rk0_sz = 1 << tmp;
|
|
}
|
|
ch1_rk0_sz = ch0_rk0_sz;
|
|
if (s6s->rank1_size_MB)
|
|
ch0_rk1_sz = s6s->rank1_size_MB;
|
|
else {
|
|
tmp = s6s->rank1_row_width + s6s->rank1_bank_width;
|
|
tmp += s6s->rank1_col_width + s6s->dw32_en ? 2 : 1;
|
|
tmp -= 20;
|
|
ch0_rk1_sz = 1 << tmp;
|
|
}
|
|
ch1_rk1_sz = ch0_rk1_sz;
|
|
|
|
cen->max = ch0_rk0_sz;
|
|
if (s6s->dual_rank_en)
|
|
cen->max += ch0_rk1_sz;
|
|
if (s6s->chn_en)
|
|
cen->max += ch1_rk0_sz + ((s6s->dual_rank_en) ? ch1_rk1_sz : 0);
|
|
if (cen->disph)
|
|
cen->max *= 2;
|
|
cen->max = cen->max << 20;
|
|
}
|
|
|
|
/*
|
|
* use_a2d_magic_v2: a helper function to calculate the input address
|
|
* for the mtk_emicen_addr2dram_v2() function
|
|
*
|
|
* There is no code comment for the translation. This is intended since
|
|
* the fomular of translation is derived from the implementation of EMI.
|
|
*/
|
|
static inline unsigned int use_a2d_magic_v2(unsigned long addr,
|
|
unsigned long magic,
|
|
unsigned int bit)
|
|
{
|
|
unsigned int ret;
|
|
|
|
ret = test_bit(bit, &addr) ? 1 : 0;
|
|
ret ^= (test_bit(16, &addr) & test_bit(0, &magic)) ? 1 : 0;
|
|
ret ^= (test_bit(17, &addr) & test_bit(1, &magic)) ? 1 : 0;
|
|
ret ^= (test_bit(18, &addr) & test_bit(2, &magic)) ? 1 : 0;
|
|
ret ^= (test_bit(19, &addr) & test_bit(3, &magic)) ? 1 : 0;
|
|
ret ^= (test_bit(20, &addr) & test_bit(4, &magic)) ? 1 : 0;
|
|
ret ^= (test_bit(21, &addr) & test_bit(5, &magic)) ? 1 : 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* a2d_rm_bit: a helper function to calculate the input address
|
|
* for the mtk_emicen_addr2dram_v2() function
|
|
*
|
|
* There is no code comment for the translation. This is intended since
|
|
* the fomular of translation is derived from the implementation of EMI.
|
|
*/
|
|
static inline unsigned long a2d_rm_bit(unsigned long taddr, int bit)
|
|
{
|
|
unsigned long ret;
|
|
|
|
ret = taddr;
|
|
clear_bit(bit, &ret);
|
|
|
|
ret = ret >> (bit + 1);
|
|
ret = ret << bit;
|
|
ret = ret & ~((1UL << bit) - 1);
|
|
|
|
ret = ret | (taddr & ((1UL << bit) - 1));
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* mtk_emicen_addr2dram_v2 - Translate a physical address to
|
|
* a DRAM-point-of-view map for EMI v2
|
|
* @addr - input physical address
|
|
* @map - output map stored in struct emi_addr_map
|
|
*
|
|
* Return 0 on success, -1 on failures.
|
|
*
|
|
* There is no code comment for the translation. This is intended since
|
|
* the fomular of translation is derived from the implementation of EMI.
|
|
*/
|
|
static int mtk_emicen_addr2dram_v2(unsigned long addr,
|
|
struct emi_addr_map *map)
|
|
{
|
|
struct a2d_s6s_v2 *s6s;
|
|
unsigned long disph, hash;
|
|
unsigned long saddr, taddr, bgaddr, noraddr;
|
|
unsigned long tmp;
|
|
int emi_tpos, chn_tpos;
|
|
|
|
if (!global_emi_cen)
|
|
return -1;
|
|
if (!map)
|
|
return -1;
|
|
if (addr < global_emi_cen->offset)
|
|
return -1;
|
|
|
|
addr -= global_emi_cen->offset;
|
|
if (addr > global_emi_cen->max)
|
|
return -1;
|
|
|
|
map->emi = -1;
|
|
map->channel = -1;
|
|
map->rank = -1;
|
|
map->bank = -1;
|
|
map->row = -1;
|
|
map->column = -1;
|
|
|
|
s6s = &global_emi_cen->a2d_s6s.v2;
|
|
disph = global_emi_cen->disph;
|
|
hash = global_emi_cen->hash;
|
|
|
|
saddr = addr;
|
|
clear_bit(9, &saddr);
|
|
clear_bit(10, &saddr);
|
|
clear_bit(11, &saddr);
|
|
clear_bit(12, &saddr);
|
|
clear_bit(13, &saddr);
|
|
clear_bit(14, &saddr);
|
|
clear_bit(15, &saddr);
|
|
clear_bit(16, &saddr);
|
|
saddr |= use_a2d_magic_v2(addr, s6s->magics[0], 9) << 9;
|
|
saddr |= use_a2d_magic_v2(addr, s6s->magics[1], 10) << 10;
|
|
saddr |= use_a2d_magic_v2(addr, s6s->magics[2], 11) << 11;
|
|
saddr |= use_a2d_magic_v2(addr, s6s->magics[3], 12) << 12;
|
|
saddr |= use_a2d_magic_v2(addr, s6s->magics[4], 13) << 13;
|
|
saddr |= use_a2d_magic_v2(addr, s6s->magics[5], 14) << 14;
|
|
saddr |= use_a2d_magic_v2(addr, s6s->magics[6], 15) << 15;
|
|
saddr |= use_a2d_magic_v2(addr, s6s->magics[7], 16) << 16;
|
|
|
|
if (!hash) {
|
|
map->channel = test_bit(s6s->chn_bit_position, &saddr) ? 1 : 0;
|
|
|
|
chn_tpos = s6s->chn_bit_position;
|
|
} else {
|
|
tmp = (test_bit(8, &saddr) && test_bit(0, &hash)) ? 1 : 0;
|
|
tmp ^= (test_bit(9, &saddr) && test_bit(1, &hash)) ? 1 : 0;
|
|
tmp ^= (test_bit(10, &saddr) && test_bit(2, &hash)) ? 1 : 0;
|
|
tmp ^= (test_bit(11, &saddr) && test_bit(3, &hash)) ? 1 : 0;
|
|
map->channel = tmp;
|
|
|
|
if (test_bit(0, &hash))
|
|
chn_tpos = 8;
|
|
else if (test_bit(1, &hash))
|
|
chn_tpos = 9;
|
|
else if (test_bit(2, &hash))
|
|
chn_tpos = 10;
|
|
else if (test_bit(3, &hash))
|
|
chn_tpos = 11;
|
|
else
|
|
chn_tpos = -1;
|
|
}
|
|
|
|
if (!disph) {
|
|
map->emi = 0;
|
|
|
|
emi_tpos = -1;
|
|
} else {
|
|
tmp = (test_bit(8, &saddr) && test_bit(0, &disph)) ? 1 : 0;
|
|
tmp ^= (test_bit(9, &saddr) && test_bit(1, &disph)) ? 1 : 0;
|
|
tmp ^= (test_bit(10, &saddr) && test_bit(2, &disph)) ? 1 : 0;
|
|
tmp ^= (test_bit(11, &saddr) && test_bit(3, &disph)) ? 1 : 0;
|
|
map->emi = tmp;
|
|
|
|
if (test_bit(0, &disph))
|
|
emi_tpos = 8;
|
|
else if (test_bit(1, &disph))
|
|
emi_tpos = 9;
|
|
else if (test_bit(2, &disph))
|
|
emi_tpos = 10;
|
|
else if (test_bit(3, &disph))
|
|
emi_tpos = 11;
|
|
else
|
|
emi_tpos = -1;
|
|
}
|
|
|
|
taddr = saddr;
|
|
if (!disph) {
|
|
if (!s6s->chn_en)
|
|
taddr = saddr;
|
|
else
|
|
taddr = a2d_rm_bit(taddr, chn_tpos);
|
|
} else {
|
|
if ((chn_tpos < 0) || (emi_tpos < 0))
|
|
return -1;
|
|
if (!s6s->chn_en)
|
|
taddr = a2d_rm_bit(taddr, emi_tpos);
|
|
else if (emi_tpos > chn_tpos) {
|
|
taddr = a2d_rm_bit(taddr, emi_tpos);
|
|
taddr = a2d_rm_bit(taddr, chn_tpos);
|
|
} else {
|
|
taddr = a2d_rm_bit(taddr, chn_tpos);
|
|
taddr = a2d_rm_bit(taddr, emi_tpos);
|
|
}
|
|
}
|
|
|
|
saddr = taddr;
|
|
clear_bit(8, &saddr);
|
|
clear_bit(11, &saddr);
|
|
clear_bit(12, &saddr);
|
|
clear_bit(13, &saddr);
|
|
clear_bit(14, &saddr);
|
|
clear_bit(15, &saddr);
|
|
clear_bit(16, &saddr);
|
|
saddr |= use_a2d_magic_v2(taddr, s6s->magics2[0], 8) << 8;
|
|
saddr |= use_a2d_magic_v2(taddr, s6s->magics2[1], 11) << 11;
|
|
saddr |= use_a2d_magic_v2(taddr, s6s->magics2[2], 12) << 12;
|
|
saddr |= use_a2d_magic_v2(taddr, s6s->magics2[3], 13) << 13;
|
|
saddr |= use_a2d_magic_v2(taddr, s6s->magics2[4], 14) << 14;
|
|
saddr |= use_a2d_magic_v2(taddr, s6s->magics2[5], 15) << 15;
|
|
saddr |= use_a2d_magic_v2(taddr, s6s->magics2[6], 16) << 16;
|
|
|
|
if (!s6s->dual_rank_en)
|
|
map->rank = 0;
|
|
else {
|
|
if (!s6s->rank_pos)
|
|
map->rank = ((saddr >> 20) > s6s->rank0_size_MB) ?
|
|
1 : 0;
|
|
else {
|
|
tmp = 1 + s6s->dw32_en;
|
|
tmp += s6s->rank0_col_width + s6s->rank0_bank_width;
|
|
map->rank = saddr >> tmp;
|
|
}
|
|
}
|
|
|
|
tmp = (map->rank)
|
|
? s6s->rank1_bg_16bank_mode
|
|
: s6s->rank0_bg_16bank_mode;
|
|
if (tmp) {
|
|
bgaddr = a2d_rm_bit(saddr, 8);
|
|
map->column = (bgaddr >> (1 + s6s->dw32_en))
|
|
% (1 << ((map->rank)
|
|
? s6s->rank1_col_width
|
|
: s6s->rank0_col_width));
|
|
|
|
tmp = (map->rank) ? s6s->rank1_col_width : s6s->rank0_col_width;
|
|
tmp = (bgaddr >> (1 + s6s->dw32_en + tmp))
|
|
% (1 << ((map->rank)
|
|
? s6s->rank1_bank_width - 1
|
|
: s6s->rank0_bank_width - 1));
|
|
map->bank = test_bit((s6s->bg1_bk3_pos) ? 0 : 1, &tmp) ? 1 : 0;
|
|
map->bank += test_bit((s6s->bg1_bk3_pos) ? 1 : 2, &tmp) ? 2 : 0;
|
|
map->bank += test_bit(8, &saddr) ? 4 : 0;
|
|
map->bank += test_bit((s6s->bg1_bk3_pos) ? 2 : 0, &tmp) ? 8 : 0;
|
|
} else {
|
|
map->column = (saddr >> (1 + s6s->dw32_en))
|
|
% (1 << ((map->rank)
|
|
? s6s->rank1_col_width
|
|
: s6s->rank0_col_width));
|
|
|
|
tmp = (map->rank) ? s6s->rank1_col_width : s6s->rank0_col_width;
|
|
map->bank = (saddr >> (1 + s6s->dw32_en + tmp))
|
|
% (1 << ((map->rank)
|
|
? s6s->rank1_bank_width
|
|
: s6s->rank0_bank_width));
|
|
}
|
|
|
|
if (!s6s->rank_pos) {
|
|
noraddr = (map->rank) ?
|
|
saddr - (s6s->rank0_size_MB << 20) : saddr;
|
|
} else {
|
|
tmp = 1 + s6s->dw32_en;
|
|
tmp += (map->rank) ?
|
|
s6s->rank1_bank_width : s6s->rank0_bank_width;
|
|
tmp += (map->rank) ?
|
|
s6s->rank1_col_width : s6s->rank0_col_width;
|
|
noraddr = a2d_rm_bit(saddr, tmp);
|
|
}
|
|
tmp = 1 + s6s->dw32_en;
|
|
tmp += (map->rank) ? s6s->rank1_bank_width : s6s->rank0_bank_width;
|
|
tmp += (map->rank) ? s6s->rank1_col_width : s6s->rank0_col_width;
|
|
noraddr = noraddr >> tmp;
|
|
tmp = (map->rank) ? s6s->rank1_row_width : s6s->rank0_row_width;
|
|
map->row = noraddr % (1 << tmp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* mtk_emicen_addr2dram - Translate a physical address to
|
|
a DRAM-point-of-view map
|
|
* @addr - input physical address
|
|
* @map - output map stored in struct emi_addr_map
|
|
*
|
|
* Return 0 on success, -1 on failures.
|
|
*
|
|
* There is no code comment for the translation. This is intended since
|
|
* the fomular of translation is derived from the implementation of EMI.
|
|
*/
|
|
int mtk_emicen_addr2dram(unsigned long addr, struct emi_addr_map *map)
|
|
{
|
|
if (!global_emi_cen)
|
|
return -1;
|
|
|
|
if (global_emi_cen->ver == 1)
|
|
return mtk_emicen_addr2dram_v1(addr, map);
|
|
else
|
|
return mtk_emicen_addr2dram_v2(addr, map);
|
|
|
|
}
|
|
EXPORT_SYMBOL(mtk_emicen_addr2dram);
|
|
|
|
static ssize_t emicen_addr2dram_show(struct device_driver *driver, char *buf)
|
|
{
|
|
int ret;
|
|
struct emi_addr_map map;
|
|
unsigned long addr;
|
|
|
|
if (!global_emi_cen)
|
|
return 0;
|
|
|
|
addr = global_emi_cen->a2d_addr;
|
|
|
|
ret = mtk_emicen_addr2dram(addr, &map);
|
|
if (!ret)
|
|
return snprintf(buf, PAGE_SIZE,
|
|
"0x%lx\n->\nemi%d\nchn%d\nrank%d\nbank%d\nrow%d\ncol%d\n",
|
|
addr, map.emi, map.channel, map.rank,
|
|
map.bank, map.row, map.column);
|
|
else
|
|
return snprintf(buf, PAGE_SIZE, "0x%lx\n->failed\n", addr);
|
|
}
|
|
|
|
static ssize_t emicen_addr2dram_store
|
|
(struct device_driver *driver, const char *buf, size_t count)
|
|
{
|
|
u64 addr;
|
|
int ret;
|
|
|
|
if (!global_emi_cen)
|
|
return count;
|
|
|
|
ret = kstrtou64(buf, 16, &addr);
|
|
if (ret)
|
|
return ret;
|
|
|
|
global_emi_cen->a2d_addr = (unsigned long)addr;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DRIVER_ATTR_RW(emicen_addr2dram);
|
|
|
|
static int emicen_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *emicen_node = pdev->dev.of_node;
|
|
struct device_node *emichn_node =
|
|
of_parse_phandle(emicen_node, "mediatek,emi-reg", 0);
|
|
struct emi_cen *cen;
|
|
unsigned int i;
|
|
int ret;
|
|
int emi_cen_cnt_temp;
|
|
|
|
pr_info("%s: module probe.\n", __func__);
|
|
|
|
cen = devm_kzalloc(&pdev->dev,
|
|
sizeof(struct emi_cen), GFP_KERNEL);
|
|
if (!cen)
|
|
return -ENOMEM;
|
|
|
|
cen->ver = (int)of_device_get_match_data(&pdev->dev);
|
|
|
|
ret = of_property_read_u32(emicen_node,
|
|
"ch_cnt", &(cen->ch_cnt));
|
|
if (ret) {
|
|
pr_info("%s: get ch_cnt fail\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_property_read_u32(emicen_node,
|
|
"rk_cnt", &(cen->rk_cnt));
|
|
if (ret) {
|
|
pr_info("%s: get rk_cnt fail\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
pr_info("%s: %s(%d), %s(%d)\n", __func__,
|
|
"ch_cnt", cen->ch_cnt,
|
|
"rk_cnt", cen->rk_cnt);
|
|
|
|
cen->rk_size = devm_kmalloc_array(&pdev->dev,
|
|
cen->rk_cnt, sizeof(unsigned long long),
|
|
GFP_KERNEL);
|
|
if (!(cen->rk_size))
|
|
return -ENOMEM;
|
|
ret = of_property_read_u64_array(emicen_node,
|
|
"rk_size", cen->rk_size, cen->rk_cnt);
|
|
|
|
for (i = 0; i < cen->rk_cnt; i++)
|
|
pr_info("%s: rk_size%d(0x%llx)\n", __func__,
|
|
i, cen->rk_size[i]);
|
|
|
|
emi_cen_cnt_temp = of_property_count_elems_of_size(
|
|
emicen_node, "reg", sizeof(unsigned int) * 4);
|
|
if (emi_cen_cnt_temp <= 0) {
|
|
pr_info("%s: get emi_cen_cnt fail\n", __func__);
|
|
return -EINVAL;
|
|
} else
|
|
cen->emi_cen_cnt = (unsigned int)emi_cen_cnt_temp;
|
|
|
|
cen->emi_cen_base = devm_kmalloc_array(&pdev->dev,
|
|
cen->emi_cen_cnt, sizeof(phys_addr_t), GFP_KERNEL);
|
|
if (!(cen->emi_cen_base))
|
|
return -ENOMEM;
|
|
for (i = 0; i < cen->emi_cen_cnt; i++)
|
|
cen->emi_cen_base[i] = of_iomap(emicen_node, i);
|
|
|
|
cen->emi_chn_base = devm_kmalloc_array(&pdev->dev,
|
|
cen->ch_cnt, sizeof(phys_addr_t), GFP_KERNEL);
|
|
if (!(cen->emi_chn_base))
|
|
return -ENOMEM;
|
|
for (i = 0; i < cen->ch_cnt; i++)
|
|
cen->emi_chn_base[i] = of_iomap(emichn_node, i);
|
|
|
|
ret = of_property_read_u32(emicen_node,
|
|
"a2d_disph", &(cen->disph));
|
|
if (ret) {
|
|
dev_info(&pdev->dev, "No a2d_disph\n");
|
|
cen->disph = MTK_EMI_DISPATCH;
|
|
}
|
|
|
|
ret = of_property_read_u32(emicen_node,
|
|
"a2d_hash", &(cen->hash));
|
|
if (ret) {
|
|
dev_info(&pdev->dev, "No a2d_hash\n");
|
|
cen->hash = MTK_EMI_HASH;
|
|
}
|
|
|
|
ret = of_property_read_u32_array(emicen_node,
|
|
"a2d_conf_offset", emi_a2d_con_offset,
|
|
ARRAY_SIZE(emi_a2d_con_offset));
|
|
if (ret)
|
|
dev_info(&pdev->dev, "No a2d_conf_offset\n");
|
|
|
|
ret = of_property_read_u32_array(emicen_node,
|
|
"a2d_chn_conf_offset", emi_a2d_chn_con_offset,
|
|
ARRAY_SIZE(emi_a2d_chn_con_offset));
|
|
if (ret)
|
|
dev_info(&pdev->dev, "No a2d_chn_conf_offset\n");
|
|
|
|
if (cen->ver == 1)
|
|
prepare_a2d_v1(cen);
|
|
else if (cen->ver == 2)
|
|
prepare_a2d_v2(cen);
|
|
else
|
|
return -ENXIO;
|
|
|
|
global_emi_cen = cen;
|
|
|
|
dev_info(&pdev->dev, "%s(%d) %s(%d), %s(%d)\n",
|
|
"version", cen->ver,
|
|
"ch_cnt", cen->ch_cnt,
|
|
"rk_cnt", cen->rk_cnt);
|
|
|
|
for (i = 0; i < cen->rk_cnt; i++)
|
|
dev_info(&pdev->dev, "rk_size%d(0x%llx)\n",
|
|
i, cen->rk_size[i]);
|
|
|
|
dev_info(&pdev->dev, "a2d_disph %d\n", cen->disph);
|
|
|
|
dev_info(&pdev->dev, "a2d_hash %d\n", cen->hash);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(emi_a2d_con_offset); i++)
|
|
dev_info(&pdev->dev, "emi_a2d_con_offset[%d] %d\n",
|
|
i, emi_a2d_con_offset[i]);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(emi_a2d_chn_con_offset); i++)
|
|
dev_info(&pdev->dev, "emi_a2d_chn_con_offset[%d] %d\n",
|
|
i, emi_a2d_chn_con_offset[i]);
|
|
|
|
platform_set_drvdata(pdev, cen);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int emicen_remove(struct platform_device *dev)
|
|
{
|
|
global_emi_cen = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id emicen_of_ids[] = {
|
|
{.compatible = "mediatek,common-emicen", .data = (void *)1 },
|
|
{.compatible = "mediatek,mt6873-emicen", .data = (void *)1 },
|
|
{.compatible = "mediatek,mt6877-emicen", .data = (void *)2 },
|
|
{}
|
|
};
|
|
|
|
static struct platform_driver emicen_drv = {
|
|
.probe = emicen_probe,
|
|
.remove = emicen_remove,
|
|
.driver = {
|
|
.name = "emicen_drv",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = emicen_of_ids,
|
|
},
|
|
};
|
|
|
|
static int __init emicen_drv_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = platform_driver_register(&emicen_drv);
|
|
if (ret) {
|
|
pr_info("%s: init fail, ret 0x%x\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = driver_create_file(&emicen_drv.driver,
|
|
&driver_attr_emicen_addr2dram);
|
|
if (ret) {
|
|
pr_info("emicen: failed to create addr2dram file\n");
|
|
return ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void __exit emicen_drv_exit(void)
|
|
{
|
|
platform_driver_unregister(&emicen_drv);
|
|
}
|
|
|
|
module_init(emicen_drv_init);
|
|
module_exit(emicen_drv_exit);
|
|
|
|
/*
|
|
* mtk_emicen_get_ch_cnt - get the channel count
|
|
*
|
|
* Returns the channel count
|
|
*/
|
|
unsigned int mtk_emicen_get_ch_cnt(void)
|
|
{
|
|
return (global_emi_cen) ? global_emi_cen->ch_cnt : 0;
|
|
}
|
|
EXPORT_SYMBOL(mtk_emicen_get_ch_cnt);
|
|
|
|
/*
|
|
* mtk_emicen_get_rk_cnt - get the rank count
|
|
*
|
|
* Returns the rank count
|
|
*/
|
|
unsigned int mtk_emicen_get_rk_cnt(void)
|
|
{
|
|
return (global_emi_cen) ? global_emi_cen->rk_cnt : 0;
|
|
}
|
|
EXPORT_SYMBOL(mtk_emicen_get_rk_cnt);
|
|
|
|
/*
|
|
* mtk_emicen_get_rk_size - get the rank size of target rank
|
|
* @rk_id: the id of target rank
|
|
*
|
|
* Returns the rank size of target rank
|
|
*/
|
|
unsigned int mtk_emicen_get_rk_size(unsigned int rk_id)
|
|
{
|
|
if (rk_id < mtk_emicen_get_rk_cnt())
|
|
return (global_emi_cen) ? global_emi_cen->rk_size[rk_id] : 0;
|
|
else
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(mtk_emicen_get_rk_size);
|
|
|
|
/*
|
|
* mtk_emidbg_dump - dump emi full status to atf log
|
|
*
|
|
*/
|
|
void mtk_emidbg_dump(void)
|
|
{
|
|
unsigned long spinlock_save_flags;
|
|
struct arm_smccc_res smc_res;
|
|
|
|
spin_lock_irqsave(&emidbg_lock, spinlock_save_flags);
|
|
|
|
arm_smccc_smc(MTK_SIP_EMIMPU_CONTROL, MTK_EMIDBG_DUMP,
|
|
0, 0, 0, 0, 0, 0, &smc_res);
|
|
while (smc_res.a0 > 0) {
|
|
arm_smccc_smc(MTK_SIP_EMIMPU_CONTROL, MTK_EMIDBG_MSG,
|
|
0, 0, 0, 0, 0, 0, &smc_res);
|
|
|
|
pr_info("%s: %d, 0x%x, 0x%x, 0x%x\n", __func__,
|
|
smc_res.a0, smc_res.a1, smc_res.a2, smc_res.a3);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&emidbg_lock, spinlock_save_flags);
|
|
}
|
|
EXPORT_SYMBOL(mtk_emidbg_dump);
|
|
|
|
MODULE_DESCRIPTION("MediaTek EMICEN Driver v0.1");
|