summaryrefslogtreecommitdiff
path: root/zephyr/shim/src/gpio_int.c
blob: 8406f3abe8b881321bd56a15d18a80803161f6a2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
/* Copyright 2022 The ChromiumOS Authors
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>

#ifdef __REQUIRE_ZEPHYR_GPIOS__
#undef __REQUIRE_ZEPHYR_GPIOS__
#endif
#include "gpio.h"
#include "gpio/gpio.h"
#include "gpio/gpio_int.h"
#include "cros_version.h"

LOG_MODULE_REGISTER(gpio_int, LOG_LEVEL_ERR);

/*
 * Structure containing the read-only configuration data for an
 * interrupt, such as the initial flags and the handler vector.
 * The RW callback data is kept in a separate array.
 */
struct gpio_int_config {
	void (*handler)(enum gpio_signal); /* Handler to call */
	gpio_flags_t flags; /* Flags */
	const struct device *port; /* GPIO device */
	gpio_pin_t pin; /* GPIO pin */
	enum gpio_signal signal; /* Signal associated with interrupt */
};

/*
 * Verify there is only a single interrupt node.
 */
#if DT_HAS_COMPAT_STATUS_OKAY(cros_ec_gpio_interrupts)
BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(cros_ec_gpio_interrupts) == 1,
	     "Only one node for cros_ec_gpio_interrupts is allowed");
#endif

/*
 * Shorthand to get the node containing the interrupt DTS.
 */
#define DT_IRQ_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(cros_ec_gpio_interrupts)

/*
 * Declare all the external handlers.
 */

#define INT_HANDLER_DECLARE(id) \
	extern void DT_STRING_TOKEN(id, handler)(enum gpio_signal);

#if DT_HAS_COMPAT_STATUS_OKAY(cros_ec_gpio_interrupts)
DT_FOREACH_CHILD(DT_IRQ_NODE, INT_HANDLER_DECLARE)
#endif

#undef INT_HANDLER_DECLARE

/*
 * Create an array of callbacks. This is separate from the
 * configuration so that the writable data is in BSS.
 */
struct gpio_callback int_cb_data[GPIO_INT_COUNT];

/*
 * Create an instance of a gpio_int_config structure from a DTS node
 */

#define INT_CONFIG_ENTRY(id, irq_pin)                                \
	{                                                            \
		.handler = DT_STRING_TOKEN(id, handler),             \
		.flags = DT_PROP(id, flags),                         \
		.port = DEVICE_DT_GET(DT_GPIO_CTLR(irq_pin, gpios)), \
		.pin = DT_GPIO_PIN(irq_pin, gpios),                  \
		.signal = GPIO_SIGNAL(irq_pin),                      \
	},

#define INT_CONFIG_FROM_NODE(id) INT_CONFIG_ENTRY(id, DT_PROP(id, irq_pin))

#if DT_HAS_COMPAT_STATUS_OKAY(cros_ec_gpio_interrupts)
/*
 * Create an array of gpio_int_config containing the read-only configuration
 * for this interrupt.
 */
static const struct gpio_int_config gpio_int_data[] = {

	DT_FOREACH_CHILD(DT_IRQ_NODE, INT_CONFIG_FROM_NODE)
};
#endif

#undef INT_CONFIG_ENTRY
#undef INT_CONFIG_FROM_NODE

/*
 * Now initialize a pointer for each interrupt that points to the
 * configuration array entries. These are used externally
 * to reference the interrupts (to enable or disable).
 * These pointers are externally declared in gpio/gpio_int.h
 * and the names are referenced via a macro using the node label or
 * node id.
 */

#define INT_CONFIG_PTR_DECLARE(id)                                   \
	const struct gpio_int_config *const GPIO_INT_FROM_NODE(id) = \
		&gpio_int_data[GPIO_INT_ENUM(id)];

#if DT_HAS_COMPAT_STATUS_OKAY(cros_ec_gpio_interrupts)

DT_FOREACH_CHILD(DT_IRQ_NODE, INT_CONFIG_PTR_DECLARE)

#endif

#undef INT_CONFIG_PTR_DECLARE

/*
 * Mapping of GPIO signal to interrupt configuration block.
 */
static const struct gpio_int_config *
signal_to_interrupt(enum gpio_signal signal)
{
#if DT_HAS_COMPAT_STATUS_OKAY(cros_ec_gpio_interrupts)
	for (int i = 0; i < ARRAY_SIZE(gpio_int_data); i++) {
		if (signal == gpio_int_data[i].signal)
			return &gpio_int_data[i];
	}
#endif
	return NULL;
}

#if DT_HAS_COMPAT_STATUS_OKAY(cros_ec_gpio_interrupts)
/*
 * Callback handler.
 * Call the stored interrupt handler.
 */
static void gpio_cb_handler(const struct device *dev,
			    struct gpio_callback *cbdata, uint32_t pins)
{
	/*
	 * Retrieve the array index from the callback pointer, and
	 * use that to get the interrupt config array entry.
	 */
	const struct gpio_int_config *conf =
		&gpio_int_data[cbdata - &int_cb_data[0]];
	conf->handler(conf->signal);
}

/*
 * Enable the interrupt.
 * Check whether the callback is already installed, and if
 * not, init and add the callback before enabling the
 * interrupt.
 */
int gpio_enable_dt_interrupt(const struct gpio_int_config *conf)
{
	/*
	 * Get the callback data associated with this interrupt
	 * by calculating the index in the gpio_int_config array.
	 */
	struct gpio_callback *cb = &int_cb_data[conf - &gpio_int_data[0]];
	gpio_flags_t flags;
	/*
	 * Check whether callback has been initialised.
	 */
	if (!cb->handler) {
		/*
		 * Initialise and add the callback.
		 */
		gpio_init_callback(cb, gpio_cb_handler, BIT(conf->pin));
		gpio_add_callback(conf->port, cb);
	}
	flags = (conf->flags | GPIO_INT_ENABLE) & ~GPIO_INT_DISABLE;
	return gpio_pin_interrupt_configure(conf->port, conf->pin, flags);
}

const struct gpio_int_config *
gpio_interrupt_get_config(enum gpio_interrupts intr)
{
	return &gpio_int_data[intr];
}

/*
 * Legacy API calls to enable/disable interrupts.
 */
int gpio_enable_interrupt(enum gpio_signal signal)
{
	const struct gpio_int_config *ic = signal_to_interrupt(signal);

	if (ic == NULL)
		return -1;

	return gpio_enable_dt_interrupt(ic);
}

#endif

/*
 * Disable the interrupt by setting the GPIO_INT_DISABLE flag.
 */
int gpio_disable_dt_interrupt(const struct gpio_int_config *conf)
{
	return gpio_pin_interrupt_configure(conf->port, conf->pin,
					    GPIO_INT_DISABLE);
}

int gpio_disable_interrupt(enum gpio_signal signal)
{
	const struct gpio_int_config *ic = signal_to_interrupt(signal);

	if (ic == NULL)
		return -1;

	return gpio_disable_dt_interrupt(ic);
}