162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2019,2023 NXP
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Implementation of the SCU IRQ functions using MU.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <dt-bindings/firmware/imx/rsrc.h>
1062306a36Sopenharmony_ci#include <linux/firmware/imx/ipc.h>
1162306a36Sopenharmony_ci#include <linux/firmware/imx/sci.h>
1262306a36Sopenharmony_ci#include <linux/kobject.h>
1362306a36Sopenharmony_ci#include <linux/mailbox_client.h>
1462306a36Sopenharmony_ci#include <linux/of.h>
1562306a36Sopenharmony_ci#include <linux/suspend.h>
1662306a36Sopenharmony_ci#include <linux/sysfs.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define IMX_SC_IRQ_FUNC_ENABLE	1
1962306a36Sopenharmony_ci#define IMX_SC_IRQ_FUNC_STATUS	2
2062306a36Sopenharmony_ci#define IMX_SC_IRQ_NUM_GROUP	9
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic u32 mu_resource_id;
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistruct imx_sc_msg_irq_get_status {
2562306a36Sopenharmony_ci	struct imx_sc_rpc_msg hdr;
2662306a36Sopenharmony_ci	union {
2762306a36Sopenharmony_ci		struct {
2862306a36Sopenharmony_ci			u16 resource;
2962306a36Sopenharmony_ci			u8 group;
3062306a36Sopenharmony_ci			u8 reserved;
3162306a36Sopenharmony_ci		} __packed req;
3262306a36Sopenharmony_ci		struct {
3362306a36Sopenharmony_ci			u32 status;
3462306a36Sopenharmony_ci		} resp;
3562306a36Sopenharmony_ci	} data;
3662306a36Sopenharmony_ci};
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistruct imx_sc_msg_irq_enable {
3962306a36Sopenharmony_ci	struct imx_sc_rpc_msg hdr;
4062306a36Sopenharmony_ci	u32 mask;
4162306a36Sopenharmony_ci	u16 resource;
4262306a36Sopenharmony_ci	u8 group;
4362306a36Sopenharmony_ci	u8 enable;
4462306a36Sopenharmony_ci} __packed;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistruct scu_wakeup {
4762306a36Sopenharmony_ci	u32 mask;
4862306a36Sopenharmony_ci	u32 wakeup_src;
4962306a36Sopenharmony_ci	bool valid;
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci/* Sysfs functions */
5362306a36Sopenharmony_cistatic struct kobject *wakeup_obj;
5462306a36Sopenharmony_cistatic ssize_t wakeup_source_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
5562306a36Sopenharmony_cistatic struct kobj_attribute wakeup_source_attr =
5662306a36Sopenharmony_ci		__ATTR(wakeup_src, 0660, wakeup_source_show, NULL);
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic struct scu_wakeup scu_irq_wakeup[IMX_SC_IRQ_NUM_GROUP];
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic struct imx_sc_ipc *imx_sc_irq_ipc_handle;
6162306a36Sopenharmony_cistatic struct work_struct imx_sc_irq_work;
6262306a36Sopenharmony_cistatic BLOCKING_NOTIFIER_HEAD(imx_scu_irq_notifier_chain);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ciint imx_scu_irq_register_notifier(struct notifier_block *nb)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	return blocking_notifier_chain_register(
6762306a36Sopenharmony_ci		&imx_scu_irq_notifier_chain, nb);
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ciEXPORT_SYMBOL(imx_scu_irq_register_notifier);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ciint imx_scu_irq_unregister_notifier(struct notifier_block *nb)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	return blocking_notifier_chain_unregister(
7462306a36Sopenharmony_ci		&imx_scu_irq_notifier_chain, nb);
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ciEXPORT_SYMBOL(imx_scu_irq_unregister_notifier);
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistatic int imx_scu_irq_notifier_call_chain(unsigned long status, u8 *group)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	return blocking_notifier_call_chain(&imx_scu_irq_notifier_chain,
8162306a36Sopenharmony_ci		status, (void *)group);
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic void imx_scu_irq_work_handler(struct work_struct *work)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	u32 irq_status;
8762306a36Sopenharmony_ci	int ret;
8862306a36Sopenharmony_ci	u8 i;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	for (i = 0; i < IMX_SC_IRQ_NUM_GROUP; i++) {
9162306a36Sopenharmony_ci		if (scu_irq_wakeup[i].mask) {
9262306a36Sopenharmony_ci			scu_irq_wakeup[i].valid = false;
9362306a36Sopenharmony_ci			scu_irq_wakeup[i].wakeup_src = 0;
9462306a36Sopenharmony_ci		}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci		ret = imx_scu_irq_get_status(i, &irq_status);
9762306a36Sopenharmony_ci		if (ret) {
9862306a36Sopenharmony_ci			pr_err("get irq group %d status failed, ret %d\n",
9962306a36Sopenharmony_ci			       i, ret);
10062306a36Sopenharmony_ci			return;
10162306a36Sopenharmony_ci		}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci		if (!irq_status)
10462306a36Sopenharmony_ci			continue;
10562306a36Sopenharmony_ci		if (scu_irq_wakeup[i].mask & irq_status) {
10662306a36Sopenharmony_ci			scu_irq_wakeup[i].valid = true;
10762306a36Sopenharmony_ci			scu_irq_wakeup[i].wakeup_src = irq_status & scu_irq_wakeup[i].mask;
10862306a36Sopenharmony_ci		} else {
10962306a36Sopenharmony_ci			scu_irq_wakeup[i].wakeup_src = irq_status;
11062306a36Sopenharmony_ci		}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci		pm_system_wakeup();
11362306a36Sopenharmony_ci		imx_scu_irq_notifier_call_chain(irq_status, &i);
11462306a36Sopenharmony_ci	}
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ciint imx_scu_irq_get_status(u8 group, u32 *irq_status)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	struct imx_sc_msg_irq_get_status msg;
12062306a36Sopenharmony_ci	struct imx_sc_rpc_msg *hdr = &msg.hdr;
12162306a36Sopenharmony_ci	int ret;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	hdr->ver = IMX_SC_RPC_VERSION;
12462306a36Sopenharmony_ci	hdr->svc = IMX_SC_RPC_SVC_IRQ;
12562306a36Sopenharmony_ci	hdr->func = IMX_SC_IRQ_FUNC_STATUS;
12662306a36Sopenharmony_ci	hdr->size = 2;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	msg.data.req.resource = mu_resource_id;
12962306a36Sopenharmony_ci	msg.data.req.group = group;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	ret = imx_scu_call_rpc(imx_sc_irq_ipc_handle, &msg, true);
13262306a36Sopenharmony_ci	if (ret)
13362306a36Sopenharmony_ci		return ret;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	if (irq_status)
13662306a36Sopenharmony_ci		*irq_status = msg.data.resp.status;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	return 0;
13962306a36Sopenharmony_ci}
14062306a36Sopenharmony_ciEXPORT_SYMBOL(imx_scu_irq_get_status);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ciint imx_scu_irq_group_enable(u8 group, u32 mask, u8 enable)
14362306a36Sopenharmony_ci{
14462306a36Sopenharmony_ci	struct imx_sc_msg_irq_enable msg;
14562306a36Sopenharmony_ci	struct imx_sc_rpc_msg *hdr = &msg.hdr;
14662306a36Sopenharmony_ci	int ret;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	if (!imx_sc_irq_ipc_handle)
14962306a36Sopenharmony_ci		return -EPROBE_DEFER;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	hdr->ver = IMX_SC_RPC_VERSION;
15262306a36Sopenharmony_ci	hdr->svc = IMX_SC_RPC_SVC_IRQ;
15362306a36Sopenharmony_ci	hdr->func = IMX_SC_IRQ_FUNC_ENABLE;
15462306a36Sopenharmony_ci	hdr->size = 3;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	msg.resource = mu_resource_id;
15762306a36Sopenharmony_ci	msg.group = group;
15862306a36Sopenharmony_ci	msg.mask = mask;
15962306a36Sopenharmony_ci	msg.enable = enable;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	ret = imx_scu_call_rpc(imx_sc_irq_ipc_handle, &msg, true);
16262306a36Sopenharmony_ci	if (ret)
16362306a36Sopenharmony_ci		pr_err("enable irq failed, group %d, mask %d, ret %d\n",
16462306a36Sopenharmony_ci			group, mask, ret);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	if (enable)
16762306a36Sopenharmony_ci		scu_irq_wakeup[group].mask |= mask;
16862306a36Sopenharmony_ci	else
16962306a36Sopenharmony_ci		scu_irq_wakeup[group].mask &= ~mask;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	return ret;
17262306a36Sopenharmony_ci}
17362306a36Sopenharmony_ciEXPORT_SYMBOL(imx_scu_irq_group_enable);
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_cistatic void imx_scu_irq_callback(struct mbox_client *c, void *msg)
17662306a36Sopenharmony_ci{
17762306a36Sopenharmony_ci	schedule_work(&imx_sc_irq_work);
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cistatic ssize_t wakeup_source_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
18162306a36Sopenharmony_ci{
18262306a36Sopenharmony_ci	int i;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	for (i = 0; i < IMX_SC_IRQ_NUM_GROUP; i++) {
18562306a36Sopenharmony_ci		if (!scu_irq_wakeup[i].wakeup_src)
18662306a36Sopenharmony_ci			continue;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci		if (scu_irq_wakeup[i].valid)
18962306a36Sopenharmony_ci			sprintf(buf, "Wakeup source group = %d, irq = 0x%x\n",
19062306a36Sopenharmony_ci				i, scu_irq_wakeup[i].wakeup_src);
19162306a36Sopenharmony_ci		else
19262306a36Sopenharmony_ci			sprintf(buf, "Spurious SCU wakeup, group = %d, irq = 0x%x\n",
19362306a36Sopenharmony_ci				i, scu_irq_wakeup[i].wakeup_src);
19462306a36Sopenharmony_ci	}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	return strlen(buf);
19762306a36Sopenharmony_ci}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ciint imx_scu_enable_general_irq_channel(struct device *dev)
20062306a36Sopenharmony_ci{
20162306a36Sopenharmony_ci	struct of_phandle_args spec;
20262306a36Sopenharmony_ci	struct mbox_client *cl;
20362306a36Sopenharmony_ci	struct mbox_chan *ch;
20462306a36Sopenharmony_ci	int ret = 0, i = 0;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	ret = imx_scu_get_handle(&imx_sc_irq_ipc_handle);
20762306a36Sopenharmony_ci	if (ret)
20862306a36Sopenharmony_ci		return ret;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	cl = devm_kzalloc(dev, sizeof(*cl), GFP_KERNEL);
21162306a36Sopenharmony_ci	if (!cl)
21262306a36Sopenharmony_ci		return -ENOMEM;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	cl->dev = dev;
21562306a36Sopenharmony_ci	cl->rx_callback = imx_scu_irq_callback;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	/* SCU general IRQ uses general interrupt channel 3 */
21862306a36Sopenharmony_ci	ch = mbox_request_channel_byname(cl, "gip3");
21962306a36Sopenharmony_ci	if (IS_ERR(ch)) {
22062306a36Sopenharmony_ci		ret = PTR_ERR(ch);
22162306a36Sopenharmony_ci		dev_err(dev, "failed to request mbox chan gip3, ret %d\n", ret);
22262306a36Sopenharmony_ci		devm_kfree(dev, cl);
22362306a36Sopenharmony_ci		return ret;
22462306a36Sopenharmony_ci	}
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	INIT_WORK(&imx_sc_irq_work, imx_scu_irq_work_handler);
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	if (!of_parse_phandle_with_args(dev->of_node, "mboxes",
22962306a36Sopenharmony_ci				       "#mbox-cells", 0, &spec))
23062306a36Sopenharmony_ci		i = of_alias_get_id(spec.np, "mu");
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	/* use mu1 as general mu irq channel if failed */
23362306a36Sopenharmony_ci	if (i < 0)
23462306a36Sopenharmony_ci		i = 1;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	mu_resource_id = IMX_SC_R_MU_0A + i;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	/* Create directory under /sysfs/firmware */
23962306a36Sopenharmony_ci	wakeup_obj = kobject_create_and_add("scu_wakeup_source", firmware_kobj);
24062306a36Sopenharmony_ci	if (!wakeup_obj) {
24162306a36Sopenharmony_ci		ret = -ENOMEM;
24262306a36Sopenharmony_ci		goto free_ch;
24362306a36Sopenharmony_ci	}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	ret = sysfs_create_file(wakeup_obj, &wakeup_source_attr.attr);
24662306a36Sopenharmony_ci	if (ret) {
24762306a36Sopenharmony_ci		dev_err(dev, "Cannot create wakeup source src file......\n");
24862306a36Sopenharmony_ci		kobject_put(wakeup_obj);
24962306a36Sopenharmony_ci		goto free_ch;
25062306a36Sopenharmony_ci	}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	return 0;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_cifree_ch:
25562306a36Sopenharmony_ci	mbox_free_channel(ch);
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	return ret;
25862306a36Sopenharmony_ci}
25962306a36Sopenharmony_ciEXPORT_SYMBOL(imx_scu_enable_general_irq_channel);
260