1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright 2019,2023 NXP |
4 | * |
5 | * Implementation of the SCU IRQ functions using MU. |
6 | * |
7 | */ |
8 | |
9 | #include <dt-bindings/firmware/imx/rsrc.h> |
10 | #include <linux/firmware/imx/ipc.h> |
11 | #include <linux/firmware/imx/sci.h> |
12 | #include <linux/kobject.h> |
13 | #include <linux/mailbox_client.h> |
14 | #include <linux/of.h> |
15 | #include <linux/suspend.h> |
16 | #include <linux/sysfs.h> |
17 | |
18 | #define IMX_SC_IRQ_FUNC_ENABLE 1 |
19 | #define IMX_SC_IRQ_FUNC_STATUS 2 |
20 | #define IMX_SC_IRQ_NUM_GROUP 9 |
21 | |
22 | static u32 mu_resource_id; |
23 | |
24 | struct imx_sc_msg_irq_get_status { |
25 | struct imx_sc_rpc_msg hdr; |
26 | union { |
27 | struct { |
28 | u16 resource; |
29 | u8 group; |
30 | u8 reserved; |
31 | } __packed req; |
32 | struct { |
33 | u32 status; |
34 | } resp; |
35 | } data; |
36 | }; |
37 | |
38 | struct imx_sc_msg_irq_enable { |
39 | struct imx_sc_rpc_msg hdr; |
40 | u32 mask; |
41 | u16 resource; |
42 | u8 group; |
43 | u8 enable; |
44 | } __packed; |
45 | |
46 | struct scu_wakeup { |
47 | u32 mask; |
48 | u32 wakeup_src; |
49 | bool valid; |
50 | }; |
51 | |
52 | /* Sysfs functions */ |
53 | static struct kobject *wakeup_obj; |
54 | static ssize_t wakeup_source_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf); |
55 | static struct kobj_attribute wakeup_source_attr = |
56 | __ATTR(wakeup_src, 0660, wakeup_source_show, NULL); |
57 | |
58 | static struct scu_wakeup scu_irq_wakeup[IMX_SC_IRQ_NUM_GROUP]; |
59 | |
60 | static struct imx_sc_ipc *imx_sc_irq_ipc_handle; |
61 | static struct work_struct imx_sc_irq_work; |
62 | static BLOCKING_NOTIFIER_HEAD(imx_scu_irq_notifier_chain); |
63 | |
64 | int imx_scu_irq_register_notifier(struct notifier_block *nb) |
65 | { |
66 | return blocking_notifier_chain_register( |
67 | nh: &imx_scu_irq_notifier_chain, nb); |
68 | } |
69 | EXPORT_SYMBOL(imx_scu_irq_register_notifier); |
70 | |
71 | int imx_scu_irq_unregister_notifier(struct notifier_block *nb) |
72 | { |
73 | return blocking_notifier_chain_unregister( |
74 | nh: &imx_scu_irq_notifier_chain, nb); |
75 | } |
76 | EXPORT_SYMBOL(imx_scu_irq_unregister_notifier); |
77 | |
78 | static int imx_scu_irq_notifier_call_chain(unsigned long status, u8 *group) |
79 | { |
80 | return blocking_notifier_call_chain(nh: &imx_scu_irq_notifier_chain, |
81 | val: status, v: (void *)group); |
82 | } |
83 | |
84 | static void imx_scu_irq_work_handler(struct work_struct *work) |
85 | { |
86 | u32 irq_status; |
87 | int ret; |
88 | u8 i; |
89 | |
90 | for (i = 0; i < IMX_SC_IRQ_NUM_GROUP; i++) { |
91 | if (scu_irq_wakeup[i].mask) { |
92 | scu_irq_wakeup[i].valid = false; |
93 | scu_irq_wakeup[i].wakeup_src = 0; |
94 | } |
95 | |
96 | ret = imx_scu_irq_get_status(group: i, irq_status: &irq_status); |
97 | if (ret) { |
98 | pr_err("get irq group %d status failed, ret %d\n" , |
99 | i, ret); |
100 | return; |
101 | } |
102 | |
103 | if (!irq_status) |
104 | continue; |
105 | if (scu_irq_wakeup[i].mask & irq_status) { |
106 | scu_irq_wakeup[i].valid = true; |
107 | scu_irq_wakeup[i].wakeup_src = irq_status & scu_irq_wakeup[i].mask; |
108 | } else { |
109 | scu_irq_wakeup[i].wakeup_src = irq_status; |
110 | } |
111 | |
112 | pm_system_wakeup(); |
113 | imx_scu_irq_notifier_call_chain(status: irq_status, group: &i); |
114 | } |
115 | } |
116 | |
117 | int imx_scu_irq_get_status(u8 group, u32 *irq_status) |
118 | { |
119 | struct imx_sc_msg_irq_get_status msg; |
120 | struct imx_sc_rpc_msg *hdr = &msg.hdr; |
121 | int ret; |
122 | |
123 | hdr->ver = IMX_SC_RPC_VERSION; |
124 | hdr->svc = IMX_SC_RPC_SVC_IRQ; |
125 | hdr->func = IMX_SC_IRQ_FUNC_STATUS; |
126 | hdr->size = 2; |
127 | |
128 | msg.data.req.resource = mu_resource_id; |
129 | msg.data.req.group = group; |
130 | |
131 | ret = imx_scu_call_rpc(ipc: imx_sc_irq_ipc_handle, msg: &msg, have_resp: true); |
132 | if (ret) |
133 | return ret; |
134 | |
135 | if (irq_status) |
136 | *irq_status = msg.data.resp.status; |
137 | |
138 | return 0; |
139 | } |
140 | EXPORT_SYMBOL(imx_scu_irq_get_status); |
141 | |
142 | int imx_scu_irq_group_enable(u8 group, u32 mask, u8 enable) |
143 | { |
144 | struct imx_sc_msg_irq_enable msg; |
145 | struct imx_sc_rpc_msg *hdr = &msg.hdr; |
146 | int ret; |
147 | |
148 | if (!imx_sc_irq_ipc_handle) |
149 | return -EPROBE_DEFER; |
150 | |
151 | hdr->ver = IMX_SC_RPC_VERSION; |
152 | hdr->svc = IMX_SC_RPC_SVC_IRQ; |
153 | hdr->func = IMX_SC_IRQ_FUNC_ENABLE; |
154 | hdr->size = 3; |
155 | |
156 | msg.resource = mu_resource_id; |
157 | msg.group = group; |
158 | msg.mask = mask; |
159 | msg.enable = enable; |
160 | |
161 | ret = imx_scu_call_rpc(ipc: imx_sc_irq_ipc_handle, msg: &msg, have_resp: true); |
162 | if (ret) |
163 | pr_err("enable irq failed, group %d, mask %d, ret %d\n" , |
164 | group, mask, ret); |
165 | |
166 | if (enable) |
167 | scu_irq_wakeup[group].mask |= mask; |
168 | else |
169 | scu_irq_wakeup[group].mask &= ~mask; |
170 | |
171 | return ret; |
172 | } |
173 | EXPORT_SYMBOL(imx_scu_irq_group_enable); |
174 | |
175 | static void imx_scu_irq_callback(struct mbox_client *c, void *msg) |
176 | { |
177 | schedule_work(work: &imx_sc_irq_work); |
178 | } |
179 | |
180 | static ssize_t wakeup_source_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
181 | { |
182 | int i; |
183 | |
184 | for (i = 0; i < IMX_SC_IRQ_NUM_GROUP; i++) { |
185 | if (!scu_irq_wakeup[i].wakeup_src) |
186 | continue; |
187 | |
188 | if (scu_irq_wakeup[i].valid) |
189 | sprintf(buf, fmt: "Wakeup source group = %d, irq = 0x%x\n" , |
190 | i, scu_irq_wakeup[i].wakeup_src); |
191 | else |
192 | sprintf(buf, fmt: "Spurious SCU wakeup, group = %d, irq = 0x%x\n" , |
193 | i, scu_irq_wakeup[i].wakeup_src); |
194 | } |
195 | |
196 | return strlen(buf); |
197 | } |
198 | |
199 | int imx_scu_enable_general_irq_channel(struct device *dev) |
200 | { |
201 | struct of_phandle_args spec; |
202 | struct mbox_client *cl; |
203 | struct mbox_chan *ch; |
204 | int ret = 0, i = 0; |
205 | |
206 | ret = imx_scu_get_handle(ipc: &imx_sc_irq_ipc_handle); |
207 | if (ret) |
208 | return ret; |
209 | |
210 | cl = devm_kzalloc(dev, size: sizeof(*cl), GFP_KERNEL); |
211 | if (!cl) |
212 | return -ENOMEM; |
213 | |
214 | cl->dev = dev; |
215 | cl->rx_callback = imx_scu_irq_callback; |
216 | |
217 | /* SCU general IRQ uses general interrupt channel 3 */ |
218 | ch = mbox_request_channel_byname(cl, name: "gip3" ); |
219 | if (IS_ERR(ptr: ch)) { |
220 | ret = PTR_ERR(ptr: ch); |
221 | dev_err(dev, "failed to request mbox chan gip3, ret %d\n" , ret); |
222 | devm_kfree(dev, p: cl); |
223 | return ret; |
224 | } |
225 | |
226 | INIT_WORK(&imx_sc_irq_work, imx_scu_irq_work_handler); |
227 | |
228 | if (!of_parse_phandle_with_args(np: dev->of_node, list_name: "mboxes" , |
229 | cells_name: "#mbox-cells" , index: 0, out_args: &spec)) |
230 | i = of_alias_get_id(np: spec.np, stem: "mu" ); |
231 | |
232 | /* use mu1 as general mu irq channel if failed */ |
233 | if (i < 0) |
234 | i = 1; |
235 | |
236 | mu_resource_id = IMX_SC_R_MU_0A + i; |
237 | |
238 | /* Create directory under /sysfs/firmware */ |
239 | wakeup_obj = kobject_create_and_add(name: "scu_wakeup_source" , parent: firmware_kobj); |
240 | if (!wakeup_obj) { |
241 | ret = -ENOMEM; |
242 | goto free_ch; |
243 | } |
244 | |
245 | ret = sysfs_create_file(kobj: wakeup_obj, attr: &wakeup_source_attr.attr); |
246 | if (ret) { |
247 | dev_err(dev, "Cannot create wakeup source src file......\n" ); |
248 | kobject_put(kobj: wakeup_obj); |
249 | goto free_ch; |
250 | } |
251 | |
252 | return 0; |
253 | |
254 | free_ch: |
255 | mbox_free_channel(chan: ch); |
256 | |
257 | return ret; |
258 | } |
259 | EXPORT_SYMBOL(imx_scu_enable_general_irq_channel); |
260 | |