diff options
Diffstat (limited to 'drivers/firmware/imx/sm-misc.c')
| -rw-r--r-- | drivers/firmware/imx/sm-misc.c | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/drivers/firmware/imx/sm-misc.c b/drivers/firmware/imx/sm-misc.c new file mode 100644 index 000000000..fb8d7bdb5 --- /dev/null +++ b/drivers/firmware/imx/sm-misc.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2024 NXP + */ + +#include <linux/debugfs.h> +#include <linux/device/devres.h> +#include <linux/firmware/imx/sm.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/scmi_protocol.h> +#include <linux/scmi_imx_protocol.h> +#include <linux/seq_file.h> +#include <linux/sizes.h> + +static const struct scmi_imx_misc_proto_ops *imx_misc_ctrl_ops; +static struct scmi_protocol_handle *ph; +static struct notifier_block scmi_imx_misc_ctrl_nb; + +static const char * const rst_imx95[] = { + "cm33_lockup", "cm33_swreq", "cm7_lockup", "cm7_swreq", "fccu", + "jtag_sw", "ele", "tempsense", "wdog1", "wdog2", "wdog3", "wdog4", + "wdog5", "jtag", "cm33_exc", "bbm", "sw", "sm_err", "fusa_sreco", + "pmic", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused", "unused", "unused", "por", +}; + +static const char * const rst_imx94[] = { + "cm33_lockup", "cm33_swreq", "cm70_lockup", "cm70_swreq", "fccu", + "jtag_sw", "ele", "tempsense", "wdog1", "wdog2", "wdog3", "wdog4", + "wdog5", "jtag", "wdog6", "wdog7", "wdog8", "wo_netc", "cm33s_lockup", + "cm33s_swreq", "cm71_lockup", "cm71_swreq", "cm33_exc", "bbm", "sw", + "sm_err", "fusa_sreco", "pmic", "unused", "unused", "unused", "por", +}; + +static const struct of_device_id allowlist[] = { + { .compatible = "fsl,imx952", .data = rst_imx95 }, + { .compatible = "fsl,imx95", .data = rst_imx95 }, + { .compatible = "fsl,imx94", .data = rst_imx94 }, + { /* Sentinel */ } +}; + +int scmi_imx_misc_ctrl_set(u32 id, u32 val) +{ + if (!ph) + return -EPROBE_DEFER; + + return imx_misc_ctrl_ops->misc_ctrl_set(ph, id, 1, &val); +}; +EXPORT_SYMBOL(scmi_imx_misc_ctrl_set); + +int scmi_imx_misc_ctrl_get(u32 id, u32 *num, u32 *val) +{ + if (!ph) + return -EPROBE_DEFER; + + return imx_misc_ctrl_ops->misc_ctrl_get(ph, id, num, val); +} +EXPORT_SYMBOL(scmi_imx_misc_ctrl_get); + +static int scmi_imx_misc_ctrl_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + /* + * notifier_chain_register requires a valid notifier_block and + * valid notifier_call. SCMI_EVENT_IMX_MISC_CONTROL is needed + * to let SCMI firmware enable control events, but the hook here + * is just a dummy function to avoid kernel panic as of now. + */ + return 0; +} + +static int syslog_show(struct seq_file *file, void *priv) +{ + /* 4KB is large enough for syslog */ + void *syslog __free(kfree) = kmalloc(SZ_4K, GFP_KERNEL); + /* syslog API use num words, not num bytes */ + u16 size = SZ_4K / 4; + int ret; + + if (!syslog) + return -ENOMEM; + + if (!ph) + return -ENODEV; + + ret = imx_misc_ctrl_ops->misc_syslog(ph, &size, syslog); + if (ret) + return ret; + + seq_hex_dump(file, " ", DUMP_PREFIX_NONE, 16, sizeof(u32), syslog, size * 4, false); + seq_putc(file, '\n'); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(syslog); + +static void scmi_imx_misc_put(void *p) +{ + debugfs_remove((struct dentry *)p); +} + +static int scmi_imx_misc_get_reason(struct scmi_device *sdev) +{ + struct scmi_imx_misc_reset_reason boot, shutdown; + const char **rst; + bool system = true; + int ret; + + if (!of_machine_device_match(allowlist)) + return 0; + + rst = (const char **)of_machine_get_match_data(allowlist); + + ret = imx_misc_ctrl_ops->misc_reset_reason(ph, system, &boot, &shutdown, NULL); + if (!ret) { + if (boot.valid) + dev_info(&sdev->dev, "%s Boot reason: %s, origin: %d, errid: %d\n", + system ? "SYS" : "LM", rst[boot.reason], + boot.orig_valid ? boot.origin : -1, + boot.err_valid ? boot.errid : -1); + if (shutdown.valid) + dev_info(&sdev->dev, "%s shutdown reason: %s, origin: %d, errid: %d\n", + system ? "SYS" : "LM", rst[shutdown.reason], + shutdown.orig_valid ? shutdown.origin : -1, + shutdown.err_valid ? shutdown.errid : -1); + } else { + dev_err(&sdev->dev, "Failed to get system reset reason: %d\n", ret); + } + + system = false; + ret = imx_misc_ctrl_ops->misc_reset_reason(ph, system, &boot, &shutdown, NULL); + if (!ret) { + if (boot.valid) + dev_info(&sdev->dev, "%s Boot reason: %s, origin: %d, errid: %d\n", + system ? "SYS" : "LM", rst[boot.reason], + boot.orig_valid ? boot.origin : -1, + boot.err_valid ? boot.errid : -1); + if (shutdown.valid) + dev_info(&sdev->dev, "%s shutdown reason: %s, origin: %d, errid: %d\n", + system ? "SYS" : "LM", rst[shutdown.reason], + shutdown.orig_valid ? shutdown.origin : -1, + shutdown.err_valid ? shutdown.errid : -1); + } else { + dev_err(&sdev->dev, "Failed to get lm reset reason: %d\n", ret); + } + + return 0; +} + +static int scmi_imx_misc_ctrl_probe(struct scmi_device *sdev) +{ + const struct scmi_handle *handle = sdev->handle; + struct device_node *np = sdev->dev.of_node; + struct dentry *scmi_imx_dentry; + u32 src_id, flags; + int ret, i, num; + + if (!handle) + return -ENODEV; + + if (imx_misc_ctrl_ops) { + dev_err(&sdev->dev, "misc ctrl already initialized\n"); + return -EEXIST; + } + + imx_misc_ctrl_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_IMX_MISC, &ph); + if (IS_ERR(imx_misc_ctrl_ops)) + return PTR_ERR(imx_misc_ctrl_ops); + + num = of_property_count_u32_elems(np, "nxp,ctrl-ids"); + if (num % 2) { + dev_err(&sdev->dev, "Invalid wakeup-sources\n"); + return -EINVAL; + } + + scmi_imx_misc_ctrl_nb.notifier_call = &scmi_imx_misc_ctrl_notifier; + for (i = 0; i < num; i += 2) { + ret = of_property_read_u32_index(np, "nxp,ctrl-ids", i, &src_id); + if (ret) { + dev_err(&sdev->dev, "Failed to read ctrl-id: %i\n", i); + continue; + } + + ret = of_property_read_u32_index(np, "nxp,ctrl-ids", i + 1, &flags); + if (ret) { + dev_err(&sdev->dev, "Failed to read ctrl-id value: %d\n", i + 1); + continue; + } + + ret = handle->notify_ops->devm_event_notifier_register(sdev, SCMI_PROTOCOL_IMX_MISC, + SCMI_EVENT_IMX_MISC_CONTROL, + &src_id, + &scmi_imx_misc_ctrl_nb); + if (ret) { + dev_err(&sdev->dev, "Failed to register scmi misc event: %d\n", src_id); + } else { + ret = imx_misc_ctrl_ops->misc_ctrl_req_notify(ph, src_id, + SCMI_EVENT_IMX_MISC_CONTROL, + flags); + if (ret) + dev_err(&sdev->dev, "Failed to req notify: %d\n", src_id); + } + } + + scmi_imx_dentry = debugfs_create_dir("scmi_imx", NULL); + debugfs_create_file("syslog", 0444, scmi_imx_dentry, &sdev->dev, &syslog_fops); + + scmi_imx_misc_get_reason(sdev); + + return devm_add_action_or_reset(&sdev->dev, scmi_imx_misc_put, scmi_imx_dentry); +} + +static const struct scmi_device_id scmi_id_table[] = { + { SCMI_PROTOCOL_IMX_MISC, "imx-misc-ctrl" }, + { }, +}; +MODULE_DEVICE_TABLE(scmi, scmi_id_table); + +static struct scmi_driver scmi_imx_misc_ctrl_driver = { + .name = "scmi-imx-misc-ctrl", + .probe = scmi_imx_misc_ctrl_probe, + .id_table = scmi_id_table, +}; +module_scmi_driver(scmi_imx_misc_ctrl_driver); + +MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>"); +MODULE_DESCRIPTION("IMX SM MISC driver"); +MODULE_LICENSE("GPL"); |
