blob: 9f19bd4e142d56c4c56e89f4ba481accc050c9ed [file] [log] [blame]
/*
* MFP Init configuration driver for MMP related platform.
*
* This driver is aims to do the initialization on the MFP configuration
* which is not handled by the component driver.
* The driver user is required to add your "mfp init deivce" in your used
* DTS file, whose complatiable is "mrvl,mmp-mfp", in which you can implement
* pinctrl setting.
*
* Copyright: (C) 2024 ASR Micro.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/fs.h>
#include <linux/debugfs.h>
#include <linux/io.h>
#include <soc/asr/regs-addr.h>
#include <linux/slab.h>
#ifdef CONFIG_OPTEE
#include <linux/asr_tee_sip.h>
#endif
#define MAX_FUNC 8
#define MAX_STR_LEN 25
struct mfpr_udr_cfg {
u32 offset;
u32 value;
u32 restore;
};
struct pinfunc_data {
char pin_func_table[MAX_FUNC][MAX_STR_LEN];
char pin_name[MAX_STR_LEN];
u32 pinfunc_mask;
u32 offset;
u32 udr_cfg;
u32 udr_restore;
};
struct mfpr_data {
struct pinfunc_data *pin_data;
int pin_num;
int no_apbsd_in_d1pp;
void __iomem *io_base;
};
static struct mfpr_data mfpr_data;
#ifdef CONFIG_OPTEE
static int asr_optee_lpm_board_cfg_init(void)
{
int i, ret;
struct pinfunc_data *data = mfpr_data.pin_data;
asr_wakeup_state_set((unsigned long)mfpr_data.no_apbsd_in_d1pp);
ret = asr_mfpr_udr_cfg_init((unsigned long)mfpr_data.pin_num);
if (ret) {
pr_err("TOS init MFPR UDR config fails\n");
return ret;
}
for (i = 0; i < mfpr_data.pin_num; i++) {
if(data[i].offset && data[i].udr_cfg) {
ret = asr_mfpr_udr_cfg_add((unsigned long)data[i].offset,
(unsigned long)data[i].udr_cfg);
if (ret) {
pr_err("MFPR UDR cfg add to TOS fails\n");
return ret;
}
}
}
return 0;
}
#endif
static int pins_functbl_init(struct device_node *node)
{
struct pinfunc_data *data = NULL;
struct device_node *child, *lbc_node;
char str[MAX_STR_LEN];
struct mfpr_udr_cfg *mfpr_cfg = NULL;
int len, lbc_num, ret;
u32 subcnt = 0, val, pd_num;
for_each_child_of_node(node, child)
subcnt++;
if(!of_property_read_u32(node, "no-apbsd-in-d1pp", &val) && val) {
mfpr_data.no_apbsd_in_d1pp = 1;
}
/* parse the legacy lpm-board-cfg node */
lbc_num = 0;
lbc_node = of_find_compatible_node(NULL, NULL, "asr,lpm-board-cfg");
if (lbc_node) {
if (!of_property_read_u32(lbc_node, "wakeup-state-d1pp", &val)) {
if (val == 1)
mfpr_data.no_apbsd_in_d1pp = 1;
}
len = of_property_count_u8_elems(lbc_node, "udr-mfpr-config");
if (len > 0) {
lbc_num = len / sizeof(struct mfpr_udr_cfg);
mfpr_cfg = kzalloc(len, GFP_KERNEL);
if (!mfpr_cfg)
return -ENOMEM;
ret = of_property_read_u32_array(lbc_node, "udr-mfpr-config",
(unsigned int *)mfpr_cfg, len / sizeof(u32));
if (ret) {
kfree(mfpr_cfg);
mfpr_cfg = NULL;
lbc_num = 0;
}
}
}
pd_num = subcnt + lbc_num;
if (!pd_num)
return 0;
mfpr_data.pin_data = kzalloc(sizeof(struct pinfunc_data) * pd_num,
GFP_ATOMIC);
if (!mfpr_data.pin_data)
return -EINVAL;
data = mfpr_data.pin_data;
for_each_child_of_node(node, child) {
int tbl_idx = 0, list_idx = 0, bit = -EINVAL;
const char *pin_name = NULL;
u32 udrcfg = 0, offset = 0;
unsigned long mask;
BUG_ON(of_property_read_u32(child, "offset", &offset));
data->offset = offset;
if (of_property_read_string(child, "spec-name", &(pin_name))) {
snprintf(str, ARRAY_SIZE(str), "MFP_%03d", (offset / 4));
strcpy(data->pin_name, str);
} else {
strcpy(data->pin_name, (char *)pin_name);
}
if (!of_property_read_u32(child, "pin-mask", &data->pinfunc_mask)
&& data->pinfunc_mask) {
mask = data->pinfunc_mask;
bit = find_first_bit(&(mask), 32);
while (1) {
const char *func_name = NULL;
BUG_ON(bit >= MAX_FUNC);
if (tbl_idx >= MAX_FUNC)
break;
if (bit == tbl_idx) {
of_property_read_string_index(child,
"pin-func-list",
list_idx, &func_name);
if (func_name) {
strcpy(data->pin_func_table[tbl_idx],
(char *)func_name);
bit = find_next_bit(&(mask),
32, bit + 1);
if (bit >= 32)
break;
list_idx++;
tbl_idx++;
continue;
}
}
snprintf(str, ARRAY_SIZE(str),
"func_%d", tbl_idx);
strcpy(data->pin_func_table[tbl_idx], str);
tbl_idx++;
}
}
if (!of_property_read_u32(child, "udr-cfg", &udrcfg)) {
data->udr_cfg = udrcfg;
}
data++;
}
mfpr_data.pin_num = subcnt;
if (lbc_num) {
int i, j;
for (j = 0; j < lbc_num; j++) {
for (i = 0; i < subcnt; i++) {
if (mfpr_cfg[j].offset == mfpr_data.pin_data[i].offset) {
mfpr_data.pin_data[i].udr_cfg = mfpr_cfg[j].value;
break;
}
}
if (i == subcnt) {
mfpr_data.pin_data[mfpr_data.pin_num].offset= mfpr_cfg[j].offset;
mfpr_data.pin_data[mfpr_data.pin_num].udr_cfg= mfpr_cfg[j].value;
snprintf(str, ARRAY_SIZE(str), "MFP_%03d", (mfpr_cfg[j].offset / 4));
strcpy(mfpr_data.pin_data[mfpr_data.pin_num].pin_name, str);
mfpr_data.pin_num ++;
}
}
kfree(mfpr_cfg);
}
#ifdef CONFIG_OPTEE
asr_optee_lpm_board_cfg_init();
#endif
return 0;
}
static char *mfp_get_pin_name(u32 offset)
{
int i;
static char name[MAX_STR_LEN];
struct pinfunc_data *data = mfpr_data.pin_data;
for (i = 0; i < mfpr_data.pin_num; i++) {
if(data[i].offset == offset)
return data[i].pin_name;
}
memset(name, 0, MAX_STR_LEN);
snprintf(name, ARRAY_SIZE(name), "MFP_%03d", (offset / 4));
return name;
}
static char *mfp_get_pin_func_name(u32 offset, u32 func)
{
int i;
static char func_name[MAX_STR_LEN];
struct pinfunc_data *data = mfpr_data.pin_data;
for (i = 0; i < mfpr_data.pin_num; i++) {
if(data[i].offset == offset) {
if ((1 << func) & data[i].pinfunc_mask)
return data[i].pin_func_table[func];
}
}
memset(func_name, 0, MAX_STR_LEN);
snprintf(func_name, ARRAY_SIZE(func_name), "func_%d", func);
return func_name;
}
static u32 mfp_get_udr_cfg(u32 offset)
{
int i;
struct pinfunc_data *data = mfpr_data.pin_data;
for (i = 0; i < mfpr_data.pin_num; i++) {
if(data[i].offset == offset) {
return data[i].udr_cfg;
}
}
return 0;
}
static int mfp_setting_dump(struct seq_file *m, void *unused)
{
u32 length = ((u32 *)(m->private))[1];
int off = 0;
void __iomem *va_base = mfpr_data.io_base;
seq_printf(m, "%15s%8s %8s %15s %12s %12s %16s %19s %8s\n",
"Pin Name", "(Offset)", "MFPR", "AltFunc",
"PullConf", "DRV.STR", "LPM Mode", "EDGE DET", "UDR_CFG");
for (off = 0; off < length; off += 0x4) {
u32 val = readl_relaxed(va_base + off);
seq_printf(m, "%15s(0x%04x) : 0x%04x%17s%12s%11s%22s%18s 0x%04x\n",
mfp_get_pin_name(off), off, val,
mfp_get_pin_func_name(off, val & 7),
((val & 0x7000) == 0x6000) ?
"Wrong PULL" : (val & 0x8000) ?
((val & 0x6000) == 0) ? "Pull Float" :
((val & 0x4000) ? ((val & 0x2000) ?
"Pull Both" : "Pull High") : "Pull Low") : "No Pull",
((val & 0x1800) == 0x1800) ?
"FAST" : ((val & 0x1800) == 0x1000) ?
"MEDIUM" : ((val & 0x1800) == 0x0800) ?
"SLOW" : "SLOW0",
((val & 0x0208) == 0) ?
"No Sleep control" :
((val & 0x0208) == 0x0208) ? ((val & 0x0080) ?
"Sleep Input" : (val & 0x0100) ?
"Sleep Out High" : "Sleep Out Low") :
"UNEX Sleep Control",
((val & 0x0040) == 0x0040) ?
"No EDGE Detect" : ((val & 0x0020) == 0) ?
(((val & 0x0010) == 0) ?
"EDGE Not Set" : "EDGE Fall EN") :
(val & 0x0010) ? "EDGE Both EN" : "EDGE Rising EN",
mfp_get_udr_cfg(off));
}
return 0;
}
static int mfp_dump_open(struct inode *inode, struct file *file)
{
return single_open(file, mfp_setting_dump, inode->i_private);
}
const struct file_operations mfp_dump_fops = {
.owner = THIS_MODULE,
.open = mfp_dump_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
void asr_mfpr_udr_cfg(void)
{
int i;
struct pinfunc_data *data = mfpr_data.pin_data;
void __iomem *base = mfpr_data.io_base;
for (i = 0; i < mfpr_data.pin_num; i++) {
if (data[i].udr_cfg) {
data[i].udr_restore = readl_relaxed(base + data[i].offset);
writel_relaxed(data[i].udr_cfg, base + data[i].offset);
}
}
}
EXPORT_SYMBOL_GPL(asr_mfpr_udr_cfg);
void asr_mfpr_udr_restore(void)
{
int i;
struct pinfunc_data *data = mfpr_data.pin_data;
void __iomem *base = mfpr_data.io_base;
for (i = 0; i < mfpr_data.pin_num; i++) {
if (data[i].udr_cfg && data[i].udr_restore) {
writel_relaxed(data[i].udr_restore, base + data[i].offset);
data[i].udr_restore = 0;
}
}
}
EXPORT_SYMBOL_GPL(asr_mfpr_udr_restore);
int asr_dont_shutdown_apb_in_d1pp(void)
{
return mfpr_data.no_apbsd_in_d1pp;
}
EXPORT_SYMBOL_GPL(asr_dont_shutdown_apb_in_d1pp);
static int mfp_probe(struct platform_device *pdev)
{
static u32 mfpreg_data[2] = {0};
struct device_node *node;
node = of_find_compatible_node(NULL, NULL, "asr,mfp-leftover");
if (!node) {
pr_err("MFP DTS is not found and no init on MFP\n");
return -EINVAL;
}
if (of_property_read_u32_array(node, "reg", mfpreg_data,
ARRAY_SIZE(mfpreg_data))) {
pr_err("Cannot get MFP Reg Base and Length\n");
return -EINVAL;
}
pr_debug("MFPReg Base : 0x%x\n", mfpreg_data[0]);
pr_debug("MFPReg Length: 0x%x\n", mfpreg_data[1]);
mfpr_data.io_base = ioremap(mfpreg_data[0], mfpreg_data[1]);
debugfs_create_file("mfp_setting_dump",
S_IRUGO, NULL, mfpreg_data, &mfp_dump_fops);
if (!mfpr_data.pin_data)
pins_functbl_init(node);
pr_info("MFP Extra Configuration initialized\n");
return 0;
}
#ifdef CONFIG_OF
static struct of_device_id mfp_dt_ids[] = {
{ .compatible = "asr,mfp-leftover", },
{}
};
MODULE_DEVICE_TABLE(of, mfp_dt_ids);
#endif
static struct platform_driver mfp_driver = {
.probe = mfp_probe,
.driver = {
.name = "asr-mfp-leftover",
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = of_match_ptr(mfp_dt_ids),
#endif
},
};
int __init mfp_init(void)
{
return platform_driver_register(&mfp_driver);
}
subsys_initcall(mfp_init);
MODULE_DESCRIPTION("MFP Initialization Driver for ASR");
MODULE_LICENSE("GPL v2");