| /* |
| * 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"); |