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 	/* Notify user with NULL data pointer */
59 	if (data->callback) {
60 		data->callback(dev, data->user_data, 0, NULL);
61 	}
62 
63 	/* Clear semaphore rx_semid interrupt status and masked status */
64 	ll_hsem_clearflag_cicr(HSEM, mask_semid);
65 }
66 
stm32_hsem_mailbox_irq_config_func(const struct device * dev)67 static void stm32_hsem_mailbox_irq_config_func(const struct device *dev)
68 {
69 	ARG_UNUSED(dev);
70 
71 	IRQ_CONNECT(DT_INST_IRQN(0),
72 		    DT_INST_IRQ(0, priority),
73 		    stm32_hsem_mailbox_ipm_rx_isr, DEVICE_DT_INST_GET(0), 0);
74 
75 	irq_enable(DT_INST_IRQN(0));
76 }
77 
stm32_hsem_mailbox_ipm_send(const struct device * dev,int wait,uint32_t id,const void * buff,int size)78 int stm32_hsem_mailbox_ipm_send(const struct device *dev, int wait, uint32_t id,
79 			  const void *buff, int size)
80 {
81 	struct stm32_hsem_mailbox_data *data = dev->data;
82 
83 	ARG_UNUSED(wait);
84 	ARG_UNUSED(buff);
85 
86 	if (size) {
87 		LOG_WRN("stm32 HSEM not support data transfer");
88 		return -EMSGSIZE;
89 	}
90 
91 	if (id) {
92 		LOG_WRN("stm32 HSEM only support a single instance of mailbox");
93 		return -EINVAL;
94 	}
95 
96 	/* Lock the semaphore tx_semid */
97 	z_stm32_hsem_lock(data->tx_semid, HSEM_LOCK_DEFAULT_RETRY);
98 
99 	/**
100 	 * Release the semaphore tx_semid.
101 	 * This will trigger a HSEMx interrupt on another CPU.
102 	 */
103 	z_stm32_hsem_unlock(data->tx_semid);
104 
105 	return 0;
106 }
107 
stm32_hsem_mailbox_ipm_register_callback(const struct device * dev,ipm_callback_t cb,void * user_data)108 void stm32_hsem_mailbox_ipm_register_callback(const struct device *dev,
109 					ipm_callback_t cb,
110 					void *user_data)
111 {
112 	struct stm32_hsem_mailbox_data *data = dev->data;
113 
114 	data->callback = cb;
115 	data->user_data = user_data;
116 }
117 
stm32_hsem_mailbox_ipm_max_data_size_get(const struct device * dev)118 int stm32_hsem_mailbox_ipm_max_data_size_get(const struct device *dev)
119 {
120 	ARG_UNUSED(dev);
121 
122 	/* stm32 HSEM not support data transfer */
123 	return 0;
124 }
125 
stm32_hsem_mailbox_ipm_max_id_val_get(const struct device * dev)126 uint32_t stm32_hsem_mailbox_ipm_max_id_val_get(const struct device *dev)
127 {
128 	ARG_UNUSED(dev);
129 
130 	/* stm32 HSEM only support a single instance of mailbox */
131 	return 0;
132 }
133 
stm32_hsem_mailbox_ipm_set_enabled(const struct device * dev,int enable)134 int stm32_hsem_mailbox_ipm_set_enabled(const struct device *dev, int enable)
135 {
136 	struct stm32_hsem_mailbox_data *data = dev->data;
137 	uint32_t mask_semid = (1U << data->rx_semid);
138 
139 	if (enable) {
140 		/* Clear semaphore rx_semid interrupt status and masked status */
141 		ll_hsem_clearflag_cicr(HSEM, mask_semid);
142 		/* Enable semaphore rx_semid on HESMx interrupt */
143 		ll_hsem_enableit_cier(HSEM, mask_semid);
144 	} else {
145 		/* Disable semaphore rx_semid on HSEMx interrupt */
146 		ll_hsem_disableit_cier(HSEM, mask_semid);
147 	}
148 
149 	return 0;
150 }
151 
stm32_hsem_mailbox_init(const struct device * dev)152 static int stm32_hsem_mailbox_init(const struct device *dev)
153 {
154 	struct stm32_hsem_mailbox_data *data = dev->data;
155 	const struct stm32_hsem_mailbox_config *cfg = dev->config;
156 	const struct device *const clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE);
157 
158 	/* Config transfer semaphore */
159 	switch (CONFIG_IPM_STM32_HSEM_CPU) {
160 	case HSEM_CPU1:
161 		if (!device_is_ready(clk)) {
162 			LOG_ERR("clock control device not ready");
163 			return -ENODEV;
164 		}
165 
166 		/* Enable clock */
167 		if (clock_control_on(clk, (clock_control_subsys_t)&cfg->pclken) != 0) {
168 			LOG_WRN("Failed to enable clock");
169 			return -EIO;
170 		}
171 
172 		data->tx_semid = CFG_HW_IPM_CPU2_SEMID;
173 		data->rx_semid = CFG_HW_IPM_CPU1_SEMID;
174 		break;
175 	case HSEM_CPU2:
176 		data->tx_semid = CFG_HW_IPM_CPU1_SEMID;
177 		data->rx_semid = CFG_HW_IPM_CPU2_SEMID;
178 		break;
179 	}
180 
181 	cfg->irq_config_func(dev);
182 
183 	return 0;
184 }
185 
186 static const struct ipm_driver_api stm32_hsem_mailbox_ipm_dirver_api = {
187 	.send = stm32_hsem_mailbox_ipm_send,
188 	.register_callback = stm32_hsem_mailbox_ipm_register_callback,
189 	.max_data_size_get = stm32_hsem_mailbox_ipm_max_data_size_get,
190 	.max_id_val_get = stm32_hsem_mailbox_ipm_max_id_val_get,
191 	.set_enabled = stm32_hsem_mailbox_ipm_set_enabled,
192 };
193 
194 static const struct stm32_hsem_mailbox_config stm32_hsem_mailbox_0_config = {
195 	.irq_config_func = stm32_hsem_mailbox_irq_config_func,
196 	.pclken = {
197 		.bus = DT_INST_CLOCKS_CELL(0, bus),
198 		.enr = DT_INST_CLOCKS_CELL(0, bits)
199 	},
200 };
201 
202 
203 /*
204  * STM32 HSEM has its own LL_HSEM(low-level HSEM) API provided by the hal_stm32 module.
205  * The ipm_stm32_hsem driver only picks up two semaphore IDs from stm32_hsem.h to simulate
206  * a virtual mailbox device. So there will have only one instance.
207  */
208 #define IPM_STM32_HSEM_INIT(inst)						\
209 	BUILD_ASSERT((inst) == 0,							\
210 		     "multiple instances not supported");		\
211 	DEVICE_DT_INST_DEFINE(0,							\
212 				&stm32_hsem_mailbox_init,				\
213 				NULL,			\
214 				&stm32_hsem_mailbox_0_data,				\
215 				&stm32_hsem_mailbox_0_config,			\
216 				POST_KERNEL,							\
217 				CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,	\
218 				&stm32_hsem_mailbox_ipm_dirver_api);	\
219 
220 DT_INST_FOREACH_STATUS_OKAY(IPM_STM32_HSEM_INIT)
221