1 /*
2  * Copyright (c) 2021 BrainCo Inc.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT st_stm32_hsem_mailbox
8 
9 #include <zephyr/device.h>
10 #include <zephyr/drivers/clock_control.h>
11 #include <zephyr/drivers/ipm.h>
12 #include <zephyr/drivers/clock_control/stm32_clock_control.h>
13 
14 #include "stm32_hsem.h"
15 
16 #include <zephyr/logging/log.h>
17 #include <zephyr/irq.h>
18 LOG_MODULE_REGISTER(ipm_stm32_hsem, CONFIG_IPM_LOG_LEVEL);
19 
20 #define HSEM_CPU1                   1
21 #define HSEM_CPU2                   2
22 
23 #if CONFIG_IPM_STM32_HSEM_CPU == HSEM_CPU1
24 #define ll_hsem_enableit_cier       LL_HSEM_EnableIT_C1IER
25 #define ll_hsem_disableit_cier      LL_HSEM_DisableIT_C1IER
26 #define ll_hsem_clearflag_cicr      LL_HSEM_ClearFlag_C1ICR
27 #define ll_hsem_isactiveflag_cmisr   LL_HSEM_IsActiveFlag_C1MISR
28 #else /* HSEM_CPU2 */
29 #define ll_hsem_enableit_cier       LL_HSEM_EnableIT_C2IER
30 #define ll_hsem_disableit_cier      LL_HSEM_DisableIT_C2IER
31 #define ll_hsem_clearflag_cicr      LL_HSEM_ClearFlag_C2ICR
32 #define ll_hsem_isactiveflag_cmisr   LL_HSEM_IsActiveFlag_C2MISR
33 #endif /* CONFIG_IPM_STM32_HSEM_CPU */
34 
35 struct stm32_hsem_mailbox_config {
36 	void (*irq_config_func)(const struct device *dev);
37 	struct stm32_pclken pclken;
38 };
39 
40 struct stm32_hsem_mailbox_data {
41 	uint32_t tx_semid;
42 	uint32_t rx_semid;
43 	ipm_callback_t callback;
44 	void *user_data;
45 };
46 
47 static struct stm32_hsem_mailbox_data stm32_hsem_mailbox_0_data;
48 
stm32_hsem_mailbox_ipm_rx_isr(const struct device * dev)49 void stm32_hsem_mailbox_ipm_rx_isr(const struct device *dev)
50 {
51 	struct stm32_hsem_mailbox_data *data = dev->data;
52 	uint32_t mask_semid = (1U << data->rx_semid);
53 
54 	/* Check semaphore rx_semid interrupt status */
55 	if (!ll_hsem_isactiveflag_cmisr(HSEM, mask_semid)) {
56 		return;
57 	}
58 
59 	/* Notify user with NULL data pointer */
60 	if (data->callback) {
61 		data->callback(dev, data->user_data, 0, NULL);
62 	}
63 
64 	/* Clear semaphore rx_semid interrupt status and masked status */
65 	ll_hsem_clearflag_cicr(HSEM, mask_semid);
66 }
67 
stm32_hsem_mailbox_irq_config_func(const struct device * dev)68 static void stm32_hsem_mailbox_irq_config_func(const struct device *dev)
69 {
70 	ARG_UNUSED(dev);
71 
72 	IRQ_CONNECT(DT_INST_IRQN(0),
73 		    DT_INST_IRQ(0, priority),
74 		    stm32_hsem_mailbox_ipm_rx_isr, DEVICE_DT_INST_GET(0), 0);
75 
76 	irq_enable(DT_INST_IRQN(0));
77 }
78 
stm32_hsem_mailbox_ipm_send(const struct device * dev,int wait,uint32_t id,const void * buff,int size)79 int stm32_hsem_mailbox_ipm_send(const struct device *dev, int wait, uint32_t id,
80 			  const void *buff, int size)
81 {
82 	struct stm32_hsem_mailbox_data *data = dev->data;
83 
84 	ARG_UNUSED(wait);
85 	ARG_UNUSED(buff);
86 
87 	if (size) {
88 		LOG_WRN("stm32 HSEM not support data transfer");
89 		return -EMSGSIZE;
90 	}
91 
92 	if (id) {
93 		LOG_WRN("stm32 HSEM only support a single instance of mailbox");
94 		return -EINVAL;
95 	}
96 
97 	/* Lock the semaphore tx_semid */
98 	z_stm32_hsem_lock(data->tx_semid, HSEM_LOCK_DEFAULT_RETRY);
99 
100 	/**
101 	 * Release the semaphore tx_semid.
102 	 * This will trigger a HSEMx interrupt on another CPU.
103 	 */
104 	z_stm32_hsem_unlock(data->tx_semid);
105 
106 	return 0;
107 }
108 
stm32_hsem_mailbox_ipm_register_callback(const struct device * dev,ipm_callback_t cb,void * user_data)109 void stm32_hsem_mailbox_ipm_register_callback(const struct device *dev,
110 					ipm_callback_t cb,
111 					void *user_data)
112 {
113 	struct stm32_hsem_mailbox_data *data = dev->data;
114 
115 	data->callback = cb;
116 	data->user_data = user_data;
117 }
118 
stm32_hsem_mailbox_ipm_max_data_size_get(const struct device * dev)119 int stm32_hsem_mailbox_ipm_max_data_size_get(const struct device *dev)
120 {
121 	ARG_UNUSED(dev);
122 
123 	/* stm32 HSEM not support data transfer */
124 	return 0;
125 }
126 
stm32_hsem_mailbox_ipm_max_id_val_get(const struct device * dev)127 uint32_t stm32_hsem_mailbox_ipm_max_id_val_get(const struct device *dev)
128 {
129 	ARG_UNUSED(dev);
130 
131 	/* stm32 HSEM only support a single instance of mailbox */
132 	return 0;
133 }
134 
stm32_hsem_mailbox_ipm_set_enabled(const struct device * dev,int enable)135 int stm32_hsem_mailbox_ipm_set_enabled(const struct device *dev, int enable)
136 {
137 	struct stm32_hsem_mailbox_data *data = dev->data;
138 	uint32_t mask_semid = (1U << data->rx_semid);
139 
140 	if (enable) {
141 		/* Clear semaphore rx_semid interrupt status and masked status */
142 		ll_hsem_clearflag_cicr(HSEM, mask_semid);
143 		/* Enable semaphore rx_semid on HESMx interrupt */
144 		ll_hsem_enableit_cier(HSEM, mask_semid);
145 	} else {
146 		/* Disable semaphore rx_semid on HSEMx interrupt */
147 		ll_hsem_disableit_cier(HSEM, mask_semid);
148 	}
149 
150 	return 0;
151 }
152 
stm32_hsem_mailbox_init(const struct device * dev)153 static int stm32_hsem_mailbox_init(const struct device *dev)
154 {
155 	struct stm32_hsem_mailbox_data *data = dev->data;
156 	const struct stm32_hsem_mailbox_config *cfg = dev->config;
157 	const struct device *const clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE);
158 
159 	/* Config transfer semaphore */
160 	switch (CONFIG_IPM_STM32_HSEM_CPU) {
161 	case HSEM_CPU1:
162 		if (!device_is_ready(clk)) {
163 			LOG_ERR("clock control device not ready");
164 			return -ENODEV;
165 		}
166 
167 		/* Enable clock */
168 		if (clock_control_on(clk, (clock_control_subsys_t)&cfg->pclken) != 0) {
169 			LOG_WRN("Failed to enable clock");
170 			return -EIO;
171 		}
172 
173 		data->tx_semid = CFG_HW_IPM_CPU2_SEMID;
174 		data->rx_semid = CFG_HW_IPM_CPU1_SEMID;
175 		break;
176 	case HSEM_CPU2:
177 		data->tx_semid = CFG_HW_IPM_CPU1_SEMID;
178 		data->rx_semid = CFG_HW_IPM_CPU2_SEMID;
179 		break;
180 	}
181 
182 	cfg->irq_config_func(dev);
183 
184 	return 0;
185 }
186 
187 static DEVICE_API(ipm, stm32_hsem_mailbox_ipm_dirver_api) = {
188 	.send = stm32_hsem_mailbox_ipm_send,
189 	.register_callback = stm32_hsem_mailbox_ipm_register_callback,
190 	.max_data_size_get = stm32_hsem_mailbox_ipm_max_data_size_get,
191 	.max_id_val_get = stm32_hsem_mailbox_ipm_max_id_val_get,
192 	.set_enabled = stm32_hsem_mailbox_ipm_set_enabled,
193 };
194 
195 static const struct stm32_hsem_mailbox_config stm32_hsem_mailbox_0_config = {
196 	.irq_config_func = stm32_hsem_mailbox_irq_config_func,
197 	.pclken = {
198 		.bus = DT_INST_CLOCKS_CELL(0, bus),
199 		.enr = DT_INST_CLOCKS_CELL(0, bits)
200 	},
201 };
202 
203 
204 /*
205  * STM32 HSEM has its own LL_HSEM(low-level HSEM) API provided by the hal_stm32 module.
206  * The ipm_stm32_hsem driver only picks up two semaphore IDs from stm32_hsem.h to simulate
207  * a virtual mailbox device. So there will have only one instance.
208  */
209 #define IPM_STM32_HSEM_INIT(inst)						\
210 	BUILD_ASSERT((inst) == 0,							\
211 		     "multiple instances not supported");		\
212 	DEVICE_DT_INST_DEFINE(0,							\
213 				&stm32_hsem_mailbox_init,				\
214 				NULL,			\
215 				&stm32_hsem_mailbox_0_data,				\
216 				&stm32_hsem_mailbox_0_config,			\
217 				POST_KERNEL,							\
218 				CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,	\
219 				&stm32_hsem_mailbox_ipm_dirver_api);	\
220 
221 DT_INST_FOREACH_STATUS_OKAY(IPM_STM32_HSEM_INIT)
222