1 /*
2  * Copyright (c) 2022 Espressif Systems (Shanghai) Co., Ltd.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT espressif_esp32_ipm
8 #include "soc/dport_reg.h"
9 #include "soc/gpio_periph.h"
10 
11 #include <stdint.h>
12 #include <string.h>
13 #include <zephyr/device.h>
14 #include <zephyr/drivers/ipm.h>
15 #include <zephyr/drivers/interrupt_controller/intc_esp32.h>
16 #include <soc.h>
17 #include <zephyr/sys/atomic.h>
18 
19 #include <zephyr/logging/log.h>
20 LOG_MODULE_REGISTER(ipm_esp32, CONFIG_IPM_LOG_LEVEL);
21 
22 #define ESP32_IPM_LOCK_FREE_VAL 0xB33FFFFF
23 #define ESP32_IPM_NOOP_VAL 0xFF
24 
25 __packed struct esp32_ipm_control {
26 	uint16_t dest_cpu_msg_id[2];
27 	atomic_val_t lock;
28 };
29 
30 struct esp32_ipm_memory {
31 	uint8_t *pro_cpu_shm;
32 	uint8_t *app_cpu_shm;
33 };
34 
35 struct esp32_ipm_config {
36 	uint32_t irq_source_pro_cpu;
37 	uint32_t irq_source_app_cpu;
38 };
39 
40 struct esp32_ipm_data {
41 	ipm_callback_t cb;
42 	void *user_data;
43 	uint32_t this_core_id;
44 	uint32_t other_core_id;
45 	uint32_t shm_size;
46 	struct esp32_ipm_memory shm;
47 	struct esp32_ipm_control *control;
48 };
49 
esp32_ipm_isr(const struct device * dev)50 IRAM_ATTR static void esp32_ipm_isr(const struct device *dev)
51 {
52 	struct esp32_ipm_data *dev_data = (struct esp32_ipm_data *)dev->data;
53 	uint32_t core_id = dev_data->this_core_id;
54 
55 	/* clear interrupt flag */
56 	if (core_id == 0) {
57 		DPORT_WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_0_REG, 0);
58 	} else {
59 		DPORT_WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_1_REG, 0);
60 	}
61 
62 	/* first of all take the own of the shared memory */
63 	while (!atomic_cas(&dev_data->control->lock,
64 		ESP32_IPM_LOCK_FREE_VAL, dev_data->this_core_id))
65 		;
66 
67 	if (dev_data->cb) {
68 
69 		volatile void *shm = dev_data->shm.pro_cpu_shm;
70 
71 		if (core_id != 0) {
72 			shm = dev_data->shm.app_cpu_shm;
73 		}
74 
75 		dev_data->cb(dev,
76 				dev_data->user_data,
77 				dev_data->control->dest_cpu_msg_id[core_id],
78 				shm);
79 	}
80 
81 	/* unlock the shared memory */
82 	atomic_set(&dev_data->control->lock, ESP32_IPM_LOCK_FREE_VAL);
83 }
84 
esp32_ipm_send(const struct device * dev,int wait,uint32_t id,const void * data,int size)85 static int esp32_ipm_send(const struct device *dev, int wait, uint32_t id,
86 			 const void *data, int size)
87 {
88 	struct esp32_ipm_data *dev_data = (struct esp32_ipm_data *)dev->data;
89 
90 	if (data == NULL) {
91 		LOG_ERR("Invalid data source");
92 		return -EINVAL;
93 	}
94 
95 	if (id > 0xFFFF) {
96 		LOG_ERR("Invalid message ID format");
97 		return -EINVAL;
98 	}
99 
100 	if (dev_data->shm_size < size) {
101 		LOG_ERR("Not enough memory in IPM channel");
102 		return -ENOMEM;
103 	}
104 
105 	uint32_t key = irq_lock();
106 
107 	/* try to lock the shared memory */
108 	while (!atomic_cas(&dev_data->control->lock,
109 		ESP32_IPM_LOCK_FREE_VAL,
110 		dev_data->this_core_id)) {
111 
112 		k_busy_wait(1);
113 
114 		if ((wait != -1) && (wait > 0)) {
115 			/* lock could not be held this time, return */
116 			wait--;
117 			if (wait == 0)  {
118 				irq_unlock(key);
119 
120 				return -ETIMEDOUT;
121 			}
122 		}
123 	}
124 
125 	/* Only the lower 16bits of id are used */
126 	dev_data->control->dest_cpu_msg_id[dev_data->other_core_id] = (uint16_t)(id & 0xFFFF);
127 
128 	/* data copied, set the id and, generate interrupt in the remote core */
129 	if (dev_data->this_core_id == 0) {
130 		memcpy(dev_data->shm.app_cpu_shm, data, size);
131 		atomic_set(&dev_data->control->lock, ESP32_IPM_LOCK_FREE_VAL);
132 		LOG_DBG("Generating interrupt on remote CPU 1 from CPU 0");
133 		DPORT_WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_1_REG, DPORT_CPU_INTR_FROM_CPU_1);
134 	} else {
135 		memcpy(dev_data->shm.pro_cpu_shm, data, size);
136 		atomic_set(&dev_data->control->lock, ESP32_IPM_LOCK_FREE_VAL);
137 		LOG_DBG("Generating interrupt on remote CPU 0 from CPU 1");
138 		DPORT_WRITE_PERI_REG(DPORT_CPU_INTR_FROM_CPU_0_REG, DPORT_CPU_INTR_FROM_CPU_0);
139 	}
140 
141 	irq_unlock(key);
142 
143 	return 0;
144 }
145 
esp32_ipm_register_callback(const struct device * dev,ipm_callback_t cb,void * user_data)146 static void esp32_ipm_register_callback(const struct device *dev,
147 				       ipm_callback_t cb,
148 				       void *user_data)
149 {
150 	struct esp32_ipm_data *data = (struct esp32_ipm_data *)dev->data;
151 
152 	uint32_t key = irq_lock();
153 
154 	data->cb = cb;
155 	data->user_data = user_data;
156 
157 	irq_unlock(key);
158 }
159 
esp32_ipm_max_data_size_get(const struct device * dev)160 static int esp32_ipm_max_data_size_get(const struct device *dev)
161 {
162 	struct esp32_ipm_data *data = (struct esp32_ipm_data *)dev->data;
163 
164 	return data->shm_size;
165 }
166 
esp_32_ipm_max_id_val_get(const struct device * dev)167 static uint32_t esp_32_ipm_max_id_val_get(const struct device *dev)
168 {
169 	ARG_UNUSED(dev);
170 	return 0xFFFF;
171 }
172 
esp_32_ipm_set_enabled(const struct device * dev,int enable)173 static int esp_32_ipm_set_enabled(const struct device *dev, int enable)
174 {
175 	/* The esp32 IPM is always enabled
176 	 * but rpmsg backend needs IPM set enabled to be
177 	 * implemented so just return success here
178 	 */
179 
180 	ARG_UNUSED(dev);
181 	ARG_UNUSED(enable);
182 
183 	return 0;
184 }
185 
186 
esp32_ipm_init(const struct device * dev)187 static int esp32_ipm_init(const struct device *dev)
188 {
189 	struct esp32_ipm_data *data = (struct esp32_ipm_data *)dev->data;
190 	struct esp32_ipm_config *cfg = (struct esp32_ipm_config *)dev->config;
191 
192 	data->this_core_id = esp_core_id();
193 	data->other_core_id = (data->this_core_id  == 0) ? 1 : 0;
194 
195 	LOG_DBG("Size of IPM shared memory: %d", data->shm_size);
196 	LOG_DBG("Address of PRO_CPU IPM shared memory: %p", data->shm.pro_cpu_shm);
197 	LOG_DBG("Address of APP_CPU IPM shared memory: %p", data->shm.app_cpu_shm);
198 	LOG_DBG("Address of IPM control structure: %p", data->control);
199 
200 	/* pro_cpu is responsible to initialize the lock of shared memory */
201 	if (data->this_core_id == 0) {
202 		esp_intr_alloc(cfg->irq_source_pro_cpu,
203 			ESP_INTR_FLAG_IRAM,
204 			(intr_handler_t)esp32_ipm_isr,
205 			(void *)dev,
206 			NULL);
207 
208 		atomic_set(&data->control->lock, ESP32_IPM_LOCK_FREE_VAL);
209 	} else {
210 		/* app_cpu wait for initialization from pro_cpu, then takes it,
211 		 * after that releases
212 		 */
213 		esp_intr_alloc(cfg->irq_source_app_cpu,
214 			ESP_INTR_FLAG_IRAM,
215 			(intr_handler_t)esp32_ipm_isr,
216 			(void *)dev,
217 			NULL);
218 
219 		LOG_DBG("Waiting CPU0 to sync");
220 
221 		while (!atomic_cas(&data->control->lock,
222 			ESP32_IPM_LOCK_FREE_VAL, data->this_core_id))
223 			;
224 
225 		atomic_set(&data->control->lock, ESP32_IPM_LOCK_FREE_VAL);
226 
227 		LOG_DBG("Synchronization done");
228 
229 	}
230 
231 	return 0;
232 }
233 
234 static const struct ipm_driver_api esp32_ipm_driver_api = {
235 	.send = esp32_ipm_send,
236 	.register_callback = esp32_ipm_register_callback,
237 	.max_data_size_get = esp32_ipm_max_data_size_get,
238 	.max_id_val_get = esp_32_ipm_max_id_val_get,
239 	.set_enabled = esp_32_ipm_set_enabled
240 };
241 
242 #define ESP32_IPM_SHM_SIZE_BY_IDX(idx)		\
243 	DT_INST_PROP(idx, shared_memory_size)	\
244 
245 #define ESP32_IPM_SHM_ADDR_BY_IDX(idx)		\
246 	DT_REG_ADDR(DT_PHANDLE(DT_DRV_INST(idx), shared_memory))	\
247 
248 #define ESP32_IPM_INIT(idx)			\
249 										\
250 static struct esp32_ipm_config esp32_ipm_device_cfg_##idx = {	\
251 	.irq_source_pro_cpu = DT_INST_IRQN(idx),		\
252 	.irq_source_app_cpu = DT_INST_IRQN(idx) + 1,	\
253 };	\
254 	\
255 static struct esp32_ipm_data esp32_ipm_device_data_##idx = {	\
256 	.shm_size = ESP32_IPM_SHM_SIZE_BY_IDX(idx),		\
257 	.shm.pro_cpu_shm = (uint8_t *)ESP32_IPM_SHM_ADDR_BY_IDX(idx),		\
258 	.shm.app_cpu_shm = (uint8_t *)ESP32_IPM_SHM_ADDR_BY_IDX(idx) +	\
259 					ESP32_IPM_SHM_SIZE_BY_IDX(idx)/2,	\
260 	.control = (struct esp32_ipm_control *)DT_INST_REG_ADDR(idx),	\
261 };	\
262 	\
263 DEVICE_DT_INST_DEFINE(idx, &esp32_ipm_init, NULL,	\
264 		    &esp32_ipm_device_data_##idx, &esp32_ipm_device_cfg_##idx,	\
265 		    PRE_KERNEL_2, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,	\
266 		    &esp32_ipm_driver_api);	\
267 
268 DT_INST_FOREACH_STATUS_OKAY(ESP32_IPM_INIT);
269