1 /*
2  * Copyright (c) 2022 Dronetag
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT gpio_radio_coex
8 
9 #include <errno.h>
10 #include <soc.h>
11 
12 #include <zephyr/kernel.h>
13 #include <zephyr/device.h>
14 #include <zephyr/logging/log.h>
15 #include <zephyr/drivers/gpio.h>
16 #include <zephyr/sys/__assert.h>
17 #include <zephyr/bluetooth/hci_types.h>
18 
19 #include "controller/hal/ticker.h"
20 #include "controller/ticker/ticker.h"
21 #include "controller/include/ll.h"
22 #include "controller/ll_sw/nordic/hal/nrf5/debug.h"
23 
24 LOG_MODULE_REGISTER(coex_ticker);
25 
26 #define COEX_RADIO_WORK_DELAY_US  50
27 #define COEX_RADIO_WORK_RESCHEDULE_DELAY_US  200
28 
29 struct coex_ticker_config {
30 	struct gpio_dt_spec grant_spec;
31 	size_t grant_delay_us;
32 };
33 
34 struct coex_ticker_data {
35 	const struct device *dev;
36 	struct gpio_callback grant_irq_cb;
37 };
38 
time_slot_delay(uint32_t ticks_at_expire,uint32_t ticks_delay,ticker_timeout_func callback,void * context)39 static int time_slot_delay(uint32_t ticks_at_expire, uint32_t ticks_delay,
40 				ticker_timeout_func callback, void *context)
41 {
42 	uint8_t instance_index;
43 	uint8_t ticker_id;
44 	int err;
45 
46 	ll_coex_ticker_id_get(&instance_index, &ticker_id);
47 
48 	/* start a secondary one-shot ticker after ticks_delay,
49 	 * this will let any radio role to gracefully abort and release the
50 	 * Radio h/w.
51 	 */
52 	err = ticker_start(instance_index, /* Radio instance ticker */
53 				1, /* user id for link layer ULL_HIGH */
54 				   /* (MAYFLY_CALL_ID_WORKER) */
55 				ticker_id, /* ticker_id */
56 				ticks_at_expire, /* current tick */
57 				ticks_delay, /* one-shot delayed timeout */
58 				0, /* periodic timeout  */
59 				0, /* periodic remainder */
60 				0, /* lazy, voluntary skips */
61 				0,
62 				callback, /* handler for executing radio abort or */
63 					 /* coex work */
64 				context, /* the context for the coex operation */
65 				NULL, /* no op callback */
66 				NULL);
67 
68 	return err;
69 }
70 
71 
time_slot_callback_work(uint32_t ticks_at_expire,uint32_t ticks_drift,uint32_t remainder,uint16_t lazy,uint8_t force,void * context)72 static void time_slot_callback_work(uint32_t ticks_at_expire,
73 					uint32_t ticks_drift,
74 					uint32_t remainder,
75 					uint16_t lazy, uint8_t force,
76 					void *context)
77 {
78 	int ret;
79 	uint8_t instance_index;
80 	uint8_t ticker_id;
81 	const struct device *dev = context;
82 	const struct coex_ticker_config *config = dev->config;
83 
84 	__ASSERT(ll_radio_state_is_idle(),
85 		 "Radio is on during coex operation.\n");
86 
87 	/* Read grant pin */
88 	if (gpio_pin_get_dt(&config->grant_spec) == 0) {
89 		DEBUG_COEX_GRANT(1);
90 
91 		if (!ll_radio_state_is_idle()) {
92 			ll_radio_state_abort();
93 		}
94 		/* Schedule another check for grant pin and abort any events scheduled */
95 		time_slot_delay(ticker_ticks_now_get(),
96 				HAL_TICKER_US_TO_TICKS(COEX_RADIO_WORK_RESCHEDULE_DELAY_US),
97 				time_slot_callback_work,
98 				context);
99 	} else {
100 		LOG_DBG("COEX Inhibit Off");
101 		DEBUG_COEX_IRQ(0);
102 		DEBUG_COEX_GRANT(0);
103 
104 		/* Enable coex pin interrupt */
105 		gpio_pin_interrupt_configure_dt(&config->grant_spec, GPIO_INT_EDGE_TO_INACTIVE);
106 
107 		/* Stop the time slot ticker */
108 		ll_coex_ticker_id_get(&instance_index, &ticker_id);
109 
110 		ret = ticker_stop(instance_index, 0, ticker_id, NULL, NULL);
111 		if (ret != TICKER_STATUS_SUCCESS &&
112 			ret != TICKER_STATUS_BUSY) {
113 			__ASSERT(0, "Failed to stop ticker.\n");
114 		}
115 	}
116 }
117 
time_slot_callback_abort(uint32_t ticks_at_expire,uint32_t ticks_drift,uint32_t remainder,uint16_t lazy,uint8_t force,void * context)118 static void time_slot_callback_abort(uint32_t ticks_at_expire,
119 					 uint32_t ticks_drift,
120 					 uint32_t remainder,
121 					 uint16_t lazy, uint8_t force,
122 					 void *context)
123 {
124 	ll_radio_state_abort();
125 	time_slot_delay(ticks_at_expire,
126 			HAL_TICKER_US_TO_TICKS(COEX_RADIO_WORK_DELAY_US),
127 			time_slot_callback_work,
128 			context);
129 }
130 
coex_ticker_grant_start(const struct device * dev)131 static int coex_ticker_grant_start(const struct device *dev)
132 {
133 	const struct coex_ticker_config *cfg = dev->config;
134 	uint32_t err;
135 
136 	err = time_slot_delay(
137 		ticker_ticks_now_get(),
138 		HAL_TICKER_US_TO_TICKS(cfg->grant_delay_us - COEX_RADIO_WORK_DELAY_US),
139 		time_slot_callback_abort,
140 		(void *)dev
141 	);
142 
143 	if (err != TICKER_STATUS_SUCCESS && err != TICKER_STATUS_BUSY) {
144 		return -EBUSY;
145 	}
146 
147 	return 0;
148 }
149 
150 
coex_ticker_grant_irq_handler(const struct device * dev,struct gpio_callback * cb,uint32_t pins)151 static void coex_ticker_grant_irq_handler(const struct device *dev,
152 					struct gpio_callback *cb, uint32_t pins)
153 {
154 	ARG_UNUSED(dev);
155 	ARG_UNUSED(pins);
156 	struct coex_ticker_data *data = CONTAINER_OF(cb, struct coex_ticker_data, grant_irq_cb);
157 	const struct coex_ticker_config *config = data->dev->config;
158 
159 	LOG_DBG("COEX Inhibit IRQ Detected");
160 	DEBUG_COEX_IRQ(1);
161 	DEBUG_COEX_GRANT(0);
162 
163 	gpio_pin_interrupt_configure_dt(&config->grant_spec, GPIO_INT_DISABLE);
164 	coex_ticker_grant_start(data->dev);
165 }
166 
167 
coex_ticker_init(const struct device * dev)168 static int coex_ticker_init(const struct device *dev)
169 {
170 	const struct coex_ticker_config *config = dev->config;
171 	struct coex_ticker_data *data = dev->data;
172 	int res;
173 
174 	data->dev = dev;
175 
176 	DEBUG_COEX_INIT();
177 	res = gpio_pin_configure_dt(&config->grant_spec, GPIO_INPUT);
178 	if (res) {
179 		return res;
180 	}
181 
182 	gpio_init_callback(&data->grant_irq_cb,
183 					   coex_ticker_grant_irq_handler,
184 					   BIT(config->grant_spec.pin));
185 
186 	res = gpio_add_callback(config->grant_spec.port, &data->grant_irq_cb);
187 	if (res) {
188 		return res;
189 	}
190 
191 	res = gpio_pin_interrupt_configure_dt(&config->grant_spec, GPIO_INT_EDGE_TO_INACTIVE);
192 	if (res) {
193 		return res;
194 	}
195 
196 	return 0;
197 }
198 
199 static struct coex_ticker_config config = {
200 	.grant_spec = GPIO_DT_SPEC_INST_GET(0, grant_gpios),
201 	.grant_delay_us = DT_INST_PROP(0, grant_delay_us)
202 };
203 static struct coex_ticker_data data;
204 
205 DEVICE_DT_INST_DEFINE(0, &coex_ticker_init, NULL, &data, &config,
206 		      POST_KERNEL, 90, NULL);
207