summaryrefslogtreecommitdiff
path: root/bsp/bluetrum/libraries/hal_drivers/drv_gpio.c
blob: 9fb13071c1250190691cfe724f617c370b85c7a5 (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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
/*
 * Copyright (c) 2020-2021, Bluetrum Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author            Notes
 * 2020-11-19     greedyhao         first version
 * 2025-05-15     kurisaW           support gpio interrupt
 */

#include "drv_gpio.h"

#ifdef RT_USING_PIN

// #define DRV_DEBUG
#define LOG_TAG             "drv.gpio"
#include <drv_log.h>

struct port_info
{
    rt_uint8_t start_pin;
    rt_uint8_t delta_pin;
    rt_uint8_t total_pin;
};

/* It needs to be adjusted to the hardware. */
static const struct port_info port_table[] =
{
    {0, 8, 0},      /* PA0-PA7 */
    {0, 5, 8},      /* PB0-PB4 */
    {0, 8, 13},     /* PE0-PE7 */
    {0, 6, 21},     /* PF0-PF5 */
};

static const hal_sfr_t port_sfr[] =
{
    GPIOA_BASE,
    GPIOB_BASE,
    GPIOE_BASE,
    GPIOF_BASE,
};

static struct ab32_pin_irq pin_irq_table[8] = {0}; // For WAKEUP_CRICUIT_0 to _7
static rt_mq_t gpio_irq_mq = RT_NULL;

static rt_uint8_t _pin_port(rt_uint32_t pin)
{
    rt_uint8_t port = 0;
    for (port = 0; port < 3; port++) {
        if (pin < (port_table[port].total_pin + port_table[port].delta_pin)) {
            break;
        }
    }
    return port;
}

#define PIN_NUM(port, no)       ((rt_uint8_t)(port_table[port].total_pin + no - port_table[port].start_pin))
#define PIN_PORT(pin)           _pin_port(pin)
#define PORT_SFR(port)          (port_sfr[(port)])
#define PIN_NO(pin)             (rt_uint8_t)((pin) & 0xFu)

static rt_base_t ab32_pin_get(const char *name)
{
    rt_base_t pin = 0;
    int hw_port_num, hw_pin_num = 0;
    int i, name_len;

    name_len = rt_strlen(name);

    if ((name_len < 4) || (name_len >= 6))
    {
        return -RT_EINVAL;
    }
    if ((name[0] != 'P') || (name[2] != '.'))
    {
        return -RT_EINVAL;
    }

    if ((name[1] >= 'A') && (name[1] <= 'B'))
    {
        hw_port_num = (int)(name[1] - 'A');
    }
    else if ((name[1] >= 'E') && (name[1] <= 'G'))
    {
        hw_port_num = (int)(name[1] - 'A') - 2;  /* Without 'C' and 'D'. */
    }
    else
    {
        return -RT_EINVAL;
    }

    for (i = 3; i < name_len; i++)
    {
        hw_pin_num *= 10;
        hw_pin_num += name[i] - '0';
    }

    pin = PIN_NUM(hw_port_num, hw_pin_num);
    LOG_D("name=%s", name);
    LOG_D("hw_port_num=%d hw_pin_num=%d pin=%d", hw_port_num, hw_pin_num, pin);

    return pin;
}

static void ab32_pin_write(rt_device_t dev, rt_base_t pin, rt_base_t value)
{
    rt_uint8_t port = PIN_PORT(pin);
    rt_uint8_t gpio_pin  = pin - port_table[port].total_pin;
    hal_gpio_write(PORT_SFR(port), gpio_pin, (rt_uint8_t)value);
}

static rt_ssize_t ab32_pin_read(rt_device_t dev, rt_base_t pin)
{
    rt_uint8_t port = PIN_PORT(pin);
    rt_uint8_t gpio_pin  = pin - port_table[port].total_pin;
    return hal_gpio_read(PORT_SFR(port), gpio_pin);
}

static void ab32_pin_mode(rt_device_t dev, rt_base_t pin, rt_uint8_t mode)
{
    struct gpio_init gpio_init;
    rt_uint8_t port = PIN_PORT(pin);

    gpio_init.pin = BIT(pin - port_table[port].total_pin);
    gpio_init.de  = GPIO_DIGITAL;
    gpio_init.af_con = GPIO_AFDIS;
    LOG_D("port=%d pin=%d", port, gpio_init.pin);

    switch (mode)
    {
    case PIN_MODE_INPUT:
        gpio_init.pull = GPIO_NOPULL;
        gpio_init.dir = GPIO_DIR_INPUT;
        break;
    case PIN_MODE_INPUT_PULLUP:
        gpio_init.pull = GPIO_PULLUP;
        gpio_init.dir = GPIO_DIR_INPUT;
        break;
    case PIN_MODE_INPUT_PULLDOWN:
        gpio_init.pull = GPIO_PULLDOWN;
        gpio_init.dir = GPIO_DIR_INPUT;
        break;
    case PIN_MODE_OUTPUT:
    case PIN_MODE_OUTPUT_OD:
    default:
        gpio_init.pull = GPIO_NOPULL;
        gpio_init.dir = GPIO_DIR_OUTPUT;
        break;
    }
    hal_gpio_init(PORT_SFR(port), &gpio_init);
}

static rt_err_t get_port_pin(rt_base_t pin, rt_uint8_t *port, rt_uint8_t *hw_pin)
{
    rt_uint8_t i;
    for (i = 0; i < sizeof(port_table) / sizeof(port_table[0]); i++)
    {
        if (pin >= port_table[i].total_pin &&
            pin < port_table[i].total_pin + port_table[i].delta_pin)
        {
            *port = i;
            *hw_pin = pin - port_table[i].total_pin + port_table[i].start_pin;
            return RT_EOK;
        }
    }
    return -RT_EINVAL;
}

static rt_int32_t get_wakeup_circuit(rt_base_t pin)
{
    /* Special interrupt pins */
    if (pin == PIN_NUM(0, 7)) return WAKEUP_CRICUIT_0; // PA7
    if (pin == PIN_NUM(1, 1)) return WAKEUP_CRICUIT_1; // PB1
    if (pin == PIN_NUM(1, 2)) return WAKEUP_CRICUIT_2; // PB2
    if (pin == PIN_NUM(1, 3)) return WAKEUP_CRICUIT_3; // PB3
    if (pin == PIN_NUM(1, 4)) return WAKEUP_CRICUIT_4; // PB4
    /* WAKEUP_CRICUIT_5 is for RTC (WKO), assuming not a GPIO pin */
    /* Other pins use WAKEUP_CRICUIT_6 (falling) or WAKEUP_CRICUIT_7 (rising) */
    return -1; // Will be handled in attach_irq based on mode
}

static rt_uint32_t get_edge_select_bit(rt_uint8_t circuit)
{
    switch (circuit)
    {
        case WAKEUP_CRICUIT_0: return WAKEUP_EDGE_SELECT_0;
        case WAKEUP_CRICUIT_1: return WAKEUP_EDGE_SELECT_1;
        case WAKEUP_CRICUIT_2: return WAKEUP_EDGE_SELECT_2;
        case WAKEUP_CRICUIT_3: return WAKEUP_EDGE_SELECT_3;
        case WAKEUP_CRICUIT_4: return WAKEUP_EDGE_SELECT_4;
        case WAKEUP_CRICUIT_5: return WAKEUP_EDGE_SELECT_5;
        case WAKEUP_CRICUIT_6: return WAKEUP_EDGE_SELECT_6;
        case WAKEUP_CRICUIT_7: return WAKEUP_EDGE_SELECT_7;
        default: return 0;
    }
}

void __attribute__((section(".irq.gpio"))) ab32_pin_irq_handler(void *args)
{
    rt_interrupt_enter();
    rt_uint32_t pending = WKUPEDG;
    rt_uint8_t circuit = 0;

    for (circuit = 0; circuit <= WAKEUP_CRICUIT_7; circuit++)
    {
        if (pending & (BIT(circuit) << WAKEUP_INT_ENABLE))
        {
            /* clear pending interrupt */
            WKUPCPND = (BIT(circuit) << WAKEUP_INT_ENABLE);
            rt_mq_send(gpio_irq_mq, &circuit, sizeof(circuit));
        }
    }
    rt_interrupt_leave();
}

static void ab32_pin_irq_thread(void *parameter)
{
    rt_uint8_t circuit;
    while (1)
    {
        if (rt_mq_recv(gpio_irq_mq, &circuit, sizeof(circuit), RT_WAITING_FOREVER) == RT_EOK)
        {
            if (circuit <= WAKEUP_CRICUIT_7 && pin_irq_table[circuit].hdr)
            {
                pin_irq_table[circuit].hdr(pin_irq_table[circuit].args);
            }
        }
    }
}

static rt_err_t ab32_pin_attach_irq(struct rt_device *device, rt_int32_t pin,
                                    rt_uint32_t mode, void (*hdr)(void *args), void *args)
{
    rt_uint8_t port, hw_pin;
    rt_int32_t circuit;
    uint8_t pin_mode;

    if (get_port_pin(pin, &port, &hw_pin) != RT_EOK)
    {
        return -RT_EINVAL;
    }

    circuit = get_wakeup_circuit(pin);
    if(circuit == -1)
    {
        circuit = (mode == PIN_IRQ_MODE_FALLING) ? WAKEUP_CRICUIT_6 : WAKEUP_CRICUIT_7;
    }

    /* store handler and arguments */
    pin_irq_table[circuit].hdr = hdr;
    pin_irq_table[circuit].args = args;

    /* The interrupt source in the port is: Port_intsrc = {PG[4:0], PF[5:0], PE[7:0], PB[4:0], PA[7:0]}. */
    /* Such as:the interrupt source number of PA0 is 0, interrupt source number of PG4 is 31. */
    PORTINTEN |= BIT(pin); // Enable interrupt
    PORTINTEDG |= BIT(pin); // Edge trigger

    return RT_EOK;
}

static rt_err_t ab32_pin_dettach_irq(struct rt_device *device, rt_int32_t pin)
{
    rt_uint8_t port, hw_pin;
    rt_int32_t circuit;

    if (get_port_pin(pin, &port, &hw_pin) != RT_EOK)
    {
        return -RT_EINVAL;
    }

    circuit = get_wakeup_circuit(pin);
    if (circuit < 0)
    {
        /* assume previously assigned to WAKEUP_CRICUIT_6 or _7 */
        /* check both circuits for handler */
        if (pin_irq_table[WAKEUP_CRICUIT_6].hdr)
            circuit = WAKEUP_CRICUIT_6;
        else if (pin_irq_table[WAKEUP_CRICUIT_7].hdr)
            circuit = WAKEUP_CRICUIT_7;
        else
            return RT_EOK;
    }

    PORTINTEN &= ~BIT(circuit);
    WKUPCON &= ~BIT(circuit);
    WKUPCPND = BIT(get_edge_select_bit(circuit));

    /* clear handler */
    pin_irq_table[circuit].hdr = RT_NULL;
    pin_irq_table[circuit].args = RT_NULL;

    return RT_EOK;
}

static rt_err_t ab32_pin_irq_enable(struct rt_device *device, rt_base_t pin,
                                    rt_uint32_t enabled)
{
    rt_uint8_t port, hw_pin;
    rt_int32_t circuit;

    if (get_port_pin(pin, &port, &hw_pin) != RT_EOK)
    {
        return -RT_EINVAL;
    }

    circuit = get_wakeup_circuit(pin);
    if (circuit < 0)
    {
        if (pin_irq_table[WAKEUP_CRICUIT_6].hdr)
        {
            circuit = WAKEUP_CRICUIT_6;
        }
        else if (pin_irq_table[WAKEUP_CRICUIT_7].hdr)
        {
            circuit = WAKEUP_CRICUIT_7;
        }
        else
            return -RT_EINVAL;
    }

    rt_uint32_t edge_bit = get_edge_select_bit(circuit);

    if (enabled == RT_TRUE)
    {
        WKUPEDG = BIT(circuit) | BIT(edge_bit);
        WKUPCON = BIT(circuit) | BIT(WAKEUP_INT_ENABLE);

        WKUPCPND = BIT(circuit);

        /* install interrupt handler */
        rt_hw_interrupt_install(IRQ_GPIO_IRQ, ab32_pin_irq_handler, RT_NULL, "gpio_isr");
        rt_hw_irq_enable(IRQ_GPIO_IRQ);
    }
    else
    {
        /* disable interrupt */
        WKUPCON &= ~BIT(circuit);
    }

    return RT_EOK;
}

static int ab32_pin_irq_init(void)
{
    gpio_irq_mq = rt_mq_create("gpio_irq", sizeof(rt_uint8_t), 128, RT_IPC_FLAG_FIFO);
    if (gpio_irq_mq == RT_NULL)
    {
        return -RT_ENOMEM;
    }

    rt_thread_t tid = rt_thread_create("gpio_irq",
                                       ab32_pin_irq_thread,
                                       RT_NULL,
                                       512,
                                       15,
                                       5);
    if (tid != RT_NULL)
    {
        rt_thread_startup(tid);
    }
    else
    {
        rt_mq_delete(gpio_irq_mq);
        return -RT_ENOMEM;
    }

    return RT_EOK;
}
INIT_PREV_EXPORT(ab32_pin_irq_init);

const static struct rt_pin_ops _ab32_pin_ops =
{
    ab32_pin_mode,
    ab32_pin_write,
    ab32_pin_read,
    ab32_pin_attach_irq,
    ab32_pin_dettach_irq,
    ab32_pin_irq_enable,
    ab32_pin_get,
};

int rt_hw_pin_init(void)
{
    return rt_device_pin_register("pin", &_ab32_pin_ops, RT_NULL);
}

#endif