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