1 /*
2 * Copyright (c) 2020-2023 Intel Corporation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT intel_sedi_ipm
8
9 #include <zephyr/kernel.h>
10 #include <zephyr/device.h>
11 #include <zephyr/pm/device.h>
12 #include <zephyr/drivers/ipm.h>
13
14 #include <zephyr/logging/log.h>
15 LOG_MODULE_REGISTER(ipm_sedi, CONFIG_IPM_LOG_LEVEL);
16
17 #include "ipm_sedi.h"
18
19 extern void sedi_ipc_isr(IN sedi_ipc_t ipc_device);
20
set_ipm_dev_busy(const struct device * dev,bool is_write)21 static void set_ipm_dev_busy(const struct device *dev, bool is_write)
22 {
23 struct ipm_sedi_context *ipm = dev->data;
24 unsigned int key = irq_lock();
25
26 atomic_set_bit(&ipm->status, is_write ? IPM_WRITE_BUSY_BIT : IPM_READ_BUSY_BIT);
27 pm_device_busy_set(dev);
28 irq_unlock(key);
29 }
30
clear_ipm_dev_busy(const struct device * dev,bool is_write)31 static void clear_ipm_dev_busy(const struct device *dev, bool is_write)
32 {
33 struct ipm_sedi_context *ipm = dev->data;
34 unsigned int key = irq_lock();
35
36 atomic_clear_bit(&ipm->status, is_write ? IPM_WRITE_BUSY_BIT : IPM_READ_BUSY_BIT);
37 if ((!atomic_test_bit(&ipm->status, IPM_WRITE_BUSY_BIT))
38 && (!atomic_test_bit(&ipm->status, IPM_READ_BUSY_BIT))) {
39 pm_device_busy_clear(dev);
40 }
41 irq_unlock(key);
42 }
43
ipm_event_dispose(IN sedi_ipc_t device,IN uint32_t event,INOUT void * params)44 static void ipm_event_dispose(IN sedi_ipc_t device, IN uint32_t event, INOUT void *params)
45 {
46 const struct device *dev = (const struct device *)params;
47 struct ipm_sedi_context *ipm = dev->data;
48 uint32_t drbl_in = 0, len;
49
50 LOG_DBG("dev: %u, event: %u", device, event);
51 switch (event) {
52 case SEDI_IPC_EVENT_MSG_IN:
53 if (ipm->rx_msg_notify_cb != NULL) {
54 set_ipm_dev_busy(dev, false);
55 sedi_ipc_read_dbl(device, &drbl_in);
56 len = IPC_HEADER_GET_LENGTH(drbl_in);
57 sedi_ipc_read_msg(device, ipm->incoming_data_buf, len);
58 ipm->rx_msg_notify_cb(dev,
59 ipm->rx_msg_notify_cb_data,
60 drbl_in, ipm->incoming_data_buf);
61 } else {
62 LOG_WRN("no handler for ipm new msg");
63 }
64 break;
65 case SEDI_IPC_EVENT_MSG_PEER_ACKED:
66 if (atomic_test_bit(&ipm->status, IPM_WRITE_IN_PROC_BIT)) {
67 k_sem_give(&ipm->device_write_msg_sem);
68 } else {
69 LOG_WRN("no sending in progress, got an ack");
70 }
71 break;
72 default:
73 return;
74 }
75 }
76
ipm_init(const struct device * dev)77 static int ipm_init(const struct device *dev)
78 {
79 /* allocate resource and context*/
80 const struct ipm_sedi_config_t *info = dev->config;
81 sedi_ipc_t device = info->ipc_device;
82 struct ipm_sedi_context *ipm = dev->data;
83
84 info->irq_config();
85 k_sem_init(&ipm->device_write_msg_sem, 0, 1);
86 k_mutex_init(&ipm->device_write_lock);
87 ipm->status = 0;
88
89 sedi_ipc_init(device, ipm_event_dispose, (void *)dev);
90 atomic_set_bit(&ipm->status, IPM_PEER_READY_BIT);
91 LOG_DBG("ipm driver initialized on device: %p", dev);
92 return 0;
93 }
94
ipm_send_isr(const struct device * dev,uint32_t drbl,const void * msg,int msg_size)95 static int ipm_send_isr(const struct device *dev,
96 uint32_t drbl,
97 const void *msg,
98 int msg_size)
99 {
100 const struct ipm_sedi_config_t *info = dev->config;
101 sedi_ipc_t device = info->ipc_device;
102 uint32_t drbl_acked = 0;
103
104 sedi_ipc_write_msg(device, (uint8_t *)msg,
105 (uint32_t)msg_size);
106 sedi_ipc_write_dbl(device, drbl);
107 do {
108 sedi_ipc_read_ack_drbl(device, &drbl_acked);
109 } while ((drbl_acked & BIT(IPC_BUSY_BIT)) == 0);
110
111 return 0;
112 }
113
ipm_sedi_send(const struct device * dev,int wait,uint32_t drbl,const void * msg,int msg_size)114 static int ipm_sedi_send(const struct device *dev,
115 int wait,
116 uint32_t drbl,
117 const void *msg,
118 int msg_size)
119 {
120 __ASSERT((dev != NULL), "bad params\n");
121 const struct ipm_sedi_config_t *info = dev->config;
122 struct ipm_sedi_context *ipm = dev->data;
123 sedi_ipc_t device = info->ipc_device;
124 int ret, sedi_ret;
125
126 /* check params, check status */
127 if ((msg_size > IPC_DATA_LEN_MAX) || ((msg_size > 0) && (msg == NULL)) ||
128 ((drbl & BIT(IPC_BUSY_BIT)) == 0)) {
129 LOG_ERR("bad params when sending ipm msg on device: %p", dev);
130 return -EINVAL;
131 }
132
133 if (wait == 0) {
134 LOG_ERR("not support no wait mode when sending ipm msg");
135 return -ENOTSUP;
136 }
137
138 if (k_is_in_isr()) {
139 return ipm_send_isr(dev, drbl, msg, msg_size);
140 }
141
142 k_mutex_lock(&ipm->device_write_lock, K_FOREVER);
143 set_ipm_dev_busy(dev, true);
144
145 if (!atomic_test_bit(&ipm->status, IPM_PEER_READY_BIT)) {
146 LOG_WRN("peer is not ready");
147 ret = -EBUSY;
148 goto write_err;
149 }
150
151 /* write data regs */
152 if (msg_size > 0) {
153 sedi_ret = sedi_ipc_write_msg(device, (uint8_t *)msg,
154 (uint32_t)msg_size);
155 if (sedi_ret != SEDI_DRIVER_OK) {
156 LOG_ERR("ipm write data fail on device: %p", dev);
157 ret = -EBUSY;
158 goto write_err;
159 }
160 }
161
162 atomic_set_bit(&ipm->status, IPM_WRITE_IN_PROC_BIT);
163 /* write drbl regs to interrupt peer*/
164 sedi_ret = sedi_ipc_write_dbl(device, drbl);
165
166 if (sedi_ret != SEDI_DRIVER_OK) {
167 LOG_ERR("ipm write doorbell fail on device: %p", dev);
168 ret = -EBUSY;
169 goto func_out;
170 }
171
172 /* wait for busy-bit-consumed interrupt */
173 ret = k_sem_take(&ipm->device_write_msg_sem, K_MSEC(IPM_TIMEOUT_MS));
174 if (ret) {
175 LOG_WRN("ipm write timeout on device: %p", dev);
176 sedi_ipc_write_dbl(device, 0);
177 }
178
179 func_out:
180 atomic_clear_bit(&ipm->status, IPM_WRITE_IN_PROC_BIT);
181
182 write_err:
183 clear_ipm_dev_busy(dev, true);
184 k_mutex_unlock(&ipm->device_write_lock);
185 if (ret == 0) {
186 LOG_DBG("ipm wrote a new message on device: %p, drbl=%08x",
187 dev, drbl);
188 }
189 return ret;
190 }
191
ipm_sedi_register_callback(const struct device * dev,ipm_callback_t cb,void * user_data)192 static void ipm_sedi_register_callback(const struct device *dev, ipm_callback_t cb,
193 void *user_data)
194 {
195 __ASSERT((dev != NULL), "bad params\n");
196
197 struct ipm_sedi_context *ipm = dev->data;
198
199 if (cb == NULL) {
200 LOG_ERR("bad params when add ipm callback on device: %p", dev);
201 return;
202 }
203
204 if (ipm->rx_msg_notify_cb == NULL) {
205 ipm->rx_msg_notify_cb = cb;
206 ipm->rx_msg_notify_cb_data = user_data;
207 } else {
208 LOG_ERR("ipm rx callback already exists on device: %p", dev);
209 }
210 }
211
ipm_sedi_complete(const struct device * dev)212 static void ipm_sedi_complete(const struct device *dev)
213 {
214 int ret;
215
216 __ASSERT((dev != NULL), "bad params\n");
217
218 const struct ipm_sedi_config_t *info = dev->config;
219 sedi_ipc_t device = info->ipc_device;
220
221 ret = sedi_ipc_send_ack_drbl(device, 0);
222 if (ret != SEDI_DRIVER_OK) {
223 LOG_ERR("ipm send ack drl fail on device: %p", dev);
224 }
225
226 clear_ipm_dev_busy(dev, false);
227 }
228
ipm_sedi_get_max_data_size(const struct device * ipmdev)229 static int ipm_sedi_get_max_data_size(const struct device *ipmdev)
230 {
231 ARG_UNUSED(ipmdev);
232 return IPC_DATA_LEN_MAX;
233 }
234
ipm_sedi_get_max_id(const struct device * ipmdev)235 static uint32_t ipm_sedi_get_max_id(const struct device *ipmdev)
236 {
237 ARG_UNUSED(ipmdev);
238 return UINT32_MAX;
239 }
240
ipm_sedi_set_enable(const struct device * dev,int enable)241 static int ipm_sedi_set_enable(const struct device *dev, int enable)
242 {
243 __ASSERT((dev != NULL), "bad params\n");
244
245 const struct ipm_sedi_config_t *info = dev->config;
246
247 if (enable) {
248 irq_enable(info->irq_num);
249 } else {
250 irq_disable(info->irq_num);
251 }
252 return 0;
253 }
254
255 #if defined(CONFIG_PM_DEVICE)
ipm_power_ctrl(const struct device * dev,enum pm_device_action action)256 static int ipm_power_ctrl(const struct device *dev,
257 enum pm_device_action action)
258 {
259 return 0;
260 }
261 #endif
262
263 static DEVICE_API(ipm, ipm_funcs) = {
264 .send = ipm_sedi_send,
265 .register_callback = ipm_sedi_register_callback,
266 .max_data_size_get = ipm_sedi_get_max_data_size,
267 .max_id_val_get = ipm_sedi_get_max_id,
268 .complete = ipm_sedi_complete,
269 .set_enabled = ipm_sedi_set_enable
270 };
271
272 #define IPM_SEDI_DEV_DEFINE(n) \
273 static struct ipm_sedi_context ipm_data_##n; \
274 static void ipm_##n##_irq_config(void); \
275 static const struct ipm_sedi_config_t ipm_config_##n = { \
276 .ipc_device = DT_INST_PROP(n, peripheral_id), \
277 .irq_num = DT_INST_IRQN(n), \
278 .irq_config = ipm_##n##_irq_config, \
279 }; \
280 static void ipm_##n##_irq_config(void) \
281 { \
282 IRQ_CONNECT(DT_INST_IRQN(n), \
283 DT_INST_IRQ(n, priority), sedi_ipc_isr, \
284 DT_INST_PROP(n, peripheral_id), \
285 DT_INST_IRQ(n, sense)); \
286 } \
287 PM_DEVICE_DT_DEFINE(DT_NODELABEL(ipm##n), ipm_power_ctrl); \
288 DEVICE_DT_INST_DEFINE(n, \
289 &ipm_init, \
290 PM_DEVICE_DT_GET(DT_NODELABEL(ipm##n)), \
291 &ipm_data_##n, \
292 &ipm_config_##n, \
293 POST_KERNEL, \
294 0, \
295 &ipm_funcs);
296
297 DT_INST_FOREACH_STATUS_OKAY(IPM_SEDI_DEV_DEFINE)
298