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 &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 &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(&imx_scu_irq_notifier_chain, 81 status, (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(i, &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(irq_status, &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(imx_sc_irq_ipc_handle, &msg, 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(imx_sc_irq_ipc_handle, &msg, 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(&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, "Wakeup source group = %d, irq = 0x%x\n", 190 i, scu_irq_wakeup[i].wakeup_src); 191 else 192 sprintf(buf, "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(&imx_sc_irq_ipc_handle); 207 if (ret) 208 return ret; 209 210 cl = devm_kzalloc(dev, 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, "gip3"); 219 if (IS_ERR(ch)) { 220 ret = PTR_ERR(ch); 221 dev_err(dev, "failed to request mbox chan gip3, ret %d\n", ret); 222 devm_kfree(dev, 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(dev->of_node, "mboxes", 229 "#mbox-cells", 0, &spec)) 230 i = of_alias_get_id(spec.np, "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("scu_wakeup_source", firmware_kobj); 240 if (!wakeup_obj) { 241 ret = -ENOMEM; 242 goto free_ch; 243 } 244 245 ret = sysfs_create_file(wakeup_obj, &wakeup_source_attr.attr); 246 if (ret) { 247 dev_err(dev, "Cannot create wakeup source src file......\n"); 248 kobject_put(wakeup_obj); 249 goto free_ch; 250 } 251 252 return 0; 253 254 free_ch: 255 mbox_free_channel(ch); 256 257 return ret; 258 } 259 EXPORT_SYMBOL(imx_scu_enable_general_irq_channel); 260