339 lines
6.9 KiB
C
339 lines
6.9 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Copyright (c) 2019 MediaTek Inc.
|
||
|
* Author: Sagy Shih <sagy.shih@mediatek.com>
|
||
|
*/
|
||
|
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_irq.h>
|
||
|
#include <linux/printk.h>
|
||
|
#include <linux/proc_fs.h>
|
||
|
#include <linux/seq_file.h>
|
||
|
|
||
|
#include <mt_emi.h>
|
||
|
#include "emi_ctrl_v1.h"
|
||
|
|
||
|
static void __iomem *CEN_EMI_BASE;
|
||
|
static void __iomem *CHN_EMI_BASE[MAX_CH];
|
||
|
#ifdef ENABLE_EMI_DEBUG_API
|
||
|
static void __iomem *EMI_DBG_BASE[MAX_DBG_NR];
|
||
|
#endif
|
||
|
static void __iomem *EMI_MPU_BASE;
|
||
|
|
||
|
static struct emi_info_t emi_info;
|
||
|
static unsigned int emi_dcm;
|
||
|
|
||
|
static int emi_probe(struct platform_device *pdev);
|
||
|
|
||
|
static int ddr_info_show(struct seq_file *m, void *v)
|
||
|
{
|
||
|
unsigned char buf[128];
|
||
|
ssize_t ret;
|
||
|
unsigned int density, rank, ddr_info;
|
||
|
|
||
|
ret = 0;
|
||
|
|
||
|
for (rank = 0, density = 0; rank < get_rk_num(); rank++)
|
||
|
density += get_rank_size(rank);
|
||
|
density *= 128;
|
||
|
|
||
|
ddr_info = ((density >> 2) & 0xF00) | get_dram_mr(5);
|
||
|
|
||
|
ret += snprintf(buf + ret, sizeof(buf) - ret,
|
||
|
"ddr_info: 0x%x\n", ddr_info);
|
||
|
ret += snprintf(buf + ret, sizeof(buf) - ret,
|
||
|
"DRAM density: %d MB\n", density);
|
||
|
ret += snprintf(buf + ret, sizeof(buf) - ret,
|
||
|
"vendor ID: 0x%x\n", get_dram_mr(5));
|
||
|
|
||
|
seq_write(m, buf, ret);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ddr_info_open(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
return single_open(file, ddr_info_show, NULL);
|
||
|
}
|
||
|
|
||
|
static const struct file_operations ddr_info_proc_fops = {
|
||
|
.open = ddr_info_open,
|
||
|
.read = seq_read,
|
||
|
.llseek = seq_lseek,
|
||
|
.release = single_release,
|
||
|
};
|
||
|
|
||
|
static int emi_remove(struct platform_device *dev)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_OF
|
||
|
static const struct of_device_id emi_of_ids[] = {
|
||
|
{.compatible = "mediatek,emi",},
|
||
|
{}
|
||
|
};
|
||
|
#endif
|
||
|
|
||
|
#ifdef CONFIG_PM
|
||
|
static int emi_suspend_noirq(struct device *dev)
|
||
|
{
|
||
|
/* pr_info("[EMI] suspend\n"); */
|
||
|
|
||
|
#if ENABLE_ELM
|
||
|
suspend_elm();
|
||
|
#endif
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int emi_resume_noirq(struct device *dev)
|
||
|
{
|
||
|
/* pr_info("[EMI] resume\n"); */
|
||
|
|
||
|
#if ENABLE_ELM
|
||
|
resume_elm();
|
||
|
#endif
|
||
|
|
||
|
#ifdef DECS_ON_SSPM
|
||
|
resume_decs(CEN_EMI_BASE);
|
||
|
#endif
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct dev_pm_ops emi_pm_ops = {
|
||
|
.suspend_noirq = emi_suspend_noirq,
|
||
|
.resume_noirq = emi_resume_noirq,
|
||
|
};
|
||
|
#define EMI_PM_OPS (&emi_pm_ops)
|
||
|
#else
|
||
|
#define EMI_PM_OPS NULL
|
||
|
#endif
|
||
|
|
||
|
static struct platform_driver emi_ctrl = {
|
||
|
.probe = emi_probe,
|
||
|
.remove = emi_remove,
|
||
|
.driver = {
|
||
|
.name = "emi_ctrl",
|
||
|
.owner = THIS_MODULE,
|
||
|
.pm = EMI_PM_OPS,
|
||
|
#ifdef CONFIG_OF
|
||
|
.of_match_table = emi_of_ids,
|
||
|
#endif
|
||
|
},
|
||
|
};
|
||
|
|
||
|
__weak void plat_debug_api_init(void)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
static int emi_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct resource *res;
|
||
|
struct proc_dir_entry *proc_entry;
|
||
|
int i;
|
||
|
|
||
|
pr_info("[EMI] module probe.\n");
|
||
|
|
||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||
|
CEN_EMI_BASE = devm_ioremap_resource(&pdev->dev, res);
|
||
|
if (IS_ERR(CEN_EMI_BASE)) {
|
||
|
pr_err("[EMI] unable to map CEN_EMI_BASE\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||
|
EMI_MPU_BASE = devm_ioremap_resource(&pdev->dev, res);
|
||
|
if (IS_ERR(EMI_MPU_BASE)) {
|
||
|
pr_err("[EMI] unable to map EMI_MPU_BASE\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < MAX_CH; i++) {
|
||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 2 + i);
|
||
|
CHN_EMI_BASE[i] = devm_ioremap_resource(&pdev->dev, res);
|
||
|
if (IS_ERR(CHN_EMI_BASE[i])) {
|
||
|
pr_err("[EMI] unable to map CH%d_EMI_BASE\n", i);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
#ifdef ENABLE_EMI_DEBUG_API
|
||
|
for (i = 0; i < MAX_DBG_NR; i++) {
|
||
|
res = platform_get_resource(pdev, IORESOURCE_MEM,
|
||
|
2 + MAX_CH + i);
|
||
|
EMI_DBG_BASE[i] = devm_ioremap_resource(&pdev->dev, res);
|
||
|
if (IS_ERR(EMI_DBG_BASE[i])) {
|
||
|
pr_debug("[EMI] unable to map dbg\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
of_property_read_u32(pdev->dev.of_node,
|
||
|
"emi_dcm", &emi_dcm);
|
||
|
plat_debug_api_init();
|
||
|
#endif
|
||
|
pr_info("[EMI] get CEN_EMI_BASE @ %p\n", mt_cen_emi_base_get());
|
||
|
pr_info("[EMI] get EMI_MPU_BASE @ %p\n", mt_emi_mpu_base_get());
|
||
|
for (i = 0; i < MAX_CH; i++)
|
||
|
pr_info("[EMI] get CH%d_EMI_BASE @ %p\n",
|
||
|
i, mt_chn_emi_base_get(i));
|
||
|
|
||
|
proc_entry = proc_create("ddr_info", 0444, NULL, &ddr_info_proc_fops);
|
||
|
|
||
|
#if ENABLE_BWL
|
||
|
bwl_init(&emi_ctrl);
|
||
|
#endif
|
||
|
|
||
|
#if ENABLE_MPU
|
||
|
mpu_init(&emi_ctrl, pdev);
|
||
|
#endif
|
||
|
|
||
|
#if ENABLE_ELM
|
||
|
elm_init(&emi_ctrl, pdev);
|
||
|
#endif
|
||
|
|
||
|
#if ENABLE_MBW
|
||
|
mbw_init(&emi_ctrl);
|
||
|
#endif
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* emi_ctrl_init: module init function.
|
||
|
*/
|
||
|
static int __init emi_ctrl_init(void)
|
||
|
{
|
||
|
int ret;
|
||
|
int i;
|
||
|
struct device_node *node;
|
||
|
|
||
|
/* register EMI ctrl interface */
|
||
|
ret = platform_driver_register(&emi_ctrl);
|
||
|
if (ret)
|
||
|
pr_err("[EMI/BWL] fail to register emi_ctrl driver\n");
|
||
|
|
||
|
/* get EMI info from boot tags */
|
||
|
node = of_find_compatible_node(NULL, NULL, "mediatek,emi");
|
||
|
if (node) {
|
||
|
ret = of_property_read_u32(node,
|
||
|
"emi_info,dram_type", &(emi_info.dram_type));
|
||
|
if (ret)
|
||
|
pr_err("[EMI] fail to get dram_type\n");
|
||
|
ret = of_property_read_u32(node,
|
||
|
"emi_info,ch_num", &(emi_info.ch_num));
|
||
|
if (ret)
|
||
|
pr_err("[EMI] fail to get ch_num\n");
|
||
|
ret = of_property_read_u32(node,
|
||
|
"emi_info,rk_num", &(emi_info.rk_num));
|
||
|
if (ret)
|
||
|
pr_err("[EMI] fail to get rk_num\n");
|
||
|
ret = of_property_read_u32_array(node, "emi_info,rank_size",
|
||
|
emi_info.rank_size, MAX_RK);
|
||
|
if (ret)
|
||
|
pr_err("[EMI] fail to get rank_size\n");
|
||
|
}
|
||
|
|
||
|
pr_info("[EMI] dram_type(%d)\n", get_dram_type());
|
||
|
pr_info("[EMI] mr5(0x%x)\n", get_dram_mr(5));
|
||
|
pr_info("[EMI] ch_num(%d)\n", get_ch_num());
|
||
|
pr_info("[EMI] rk_num(%d)\n", get_rk_num());
|
||
|
for (i = 0; i < get_rk_num(); i++)
|
||
|
pr_info("[EMI] rank%d_size(0x%x)", i, get_rank_size(i));
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* emi_ctrl_exit: module exit function.
|
||
|
*/
|
||
|
static void __exit emi_ctrl_exit(void)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
postcore_initcall(emi_ctrl_init);
|
||
|
module_exit(emi_ctrl_exit);
|
||
|
|
||
|
unsigned int get_dram_type(void)
|
||
|
{
|
||
|
return (emi_info.dram_type & 0xF);
|
||
|
}
|
||
|
|
||
|
unsigned int get_dram_mr(unsigned int index)
|
||
|
{
|
||
|
switch (index) {
|
||
|
case 5:
|
||
|
return ((emi_info.dram_type >> 24) & 0xFF);
|
||
|
default:
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
unsigned int get_ch_num(void)
|
||
|
{
|
||
|
return emi_info.ch_num;
|
||
|
}
|
||
|
|
||
|
unsigned int get_rk_num(void)
|
||
|
{
|
||
|
return emi_info.rk_num;
|
||
|
}
|
||
|
|
||
|
unsigned int get_rank_size(unsigned int rank_index)
|
||
|
{
|
||
|
if ((rank_index < emi_info.rk_num) && (rank_index < MAX_RK))
|
||
|
return emi_info.rank_size[rank_index];
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void __iomem *mt_cen_emi_base_get(void)
|
||
|
{
|
||
|
return CEN_EMI_BASE;
|
||
|
}
|
||
|
EXPORT_SYMBOL(mt_cen_emi_base_get);
|
||
|
|
||
|
/* for legacy use */
|
||
|
void __iomem *mt_emi_base_get(void)
|
||
|
{
|
||
|
return mt_cen_emi_base_get();
|
||
|
}
|
||
|
EXPORT_SYMBOL(mt_emi_base_get);
|
||
|
|
||
|
void __iomem *mt_chn_emi_base_get(unsigned int channel_index)
|
||
|
{
|
||
|
if (channel_index < MAX_CH)
|
||
|
return CHN_EMI_BASE[channel_index];
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
EXPORT_SYMBOL(mt_chn_emi_base_get);
|
||
|
|
||
|
#ifdef ENABLE_EMI_DEBUG_API
|
||
|
void __iomem *mt_emi_dbg_base_get(unsigned int index)
|
||
|
{
|
||
|
if (index < MAX_DBG_NR)
|
||
|
return EMI_DBG_BASE[index];
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
EXPORT_SYMBOL(mt_emi_dbg_base_get);
|
||
|
#endif
|
||
|
|
||
|
void __iomem *mt_emi_mpu_base_get(void)
|
||
|
{
|
||
|
return EMI_MPU_BASE;
|
||
|
}
|
||
|
EXPORT_SYMBOL(mt_emi_mpu_base_get);
|
||
|
|
||
|
unsigned int mt_emi_dcm_config(void)
|
||
|
{
|
||
|
return emi_dcm;
|
||
|
}
|
||
|
EXPORT_SYMBOL(mt_emi_dcm_config);
|