aboutsummaryrefslogtreecommitdiff
path: root/src/zjs_gpio.c
blob: f3eb57dba0ca6ed3ca64df4d2aa1a77dc5d4ba5b (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
// Copyright (c) 2016-2017, Intel Corporation.

// Zephyr includes
#ifndef ZJS_LINUX_BUILD
#include <zephyr.h>
#include <gpio.h>
#include <misc/util.h>
#endif
#include <string.h>
#include <stdlib.h>

// ZJS includes
#include "zjs_gpio.h"
#include "zjs_board.h"
#include "zjs_util.h"
#include "zjs_callbacks.h"

#ifdef ZJS_GPIO_MOCK
#include "zjs_gpio_mock.h"
#else
#define DEVICE struct device *
#define zjs_gpio_mock_pre_init()
#define zjs_gpio_mock_post_init(gpio_obj)
#define zjs_gpio_mock_cleanup()
#endif

static jerry_value_t gpio_api = 0;
static jerry_value_t gpio_pin_prototype = 0;

// Handle for GPIO input pins, passed around between ISR/C callbacks
typedef struct gpio_handle {
    struct gpio_callback callback;  // Callback structure for zephyr
    DEVICE port;                    // Pin's port
    uint32_t pin;                   // Pin associated with this handle
    uint32_t value;                 // Value of the pin
    zjs_callback_id callbackId;     // ID for the C callback
    jerry_value_t pin_obj;          // Pin object returned from open()
    uint32_t last;
    uint8_t edge_both;
    bool active_low;
    bool closed;
} gpio_handle_t;

void gpio_internal_lookup_pin(const jerry_value_t pin_obj, DEVICE *port,
                              int *pin)
{
    uintptr_t ptr;
    if (jerry_get_object_native_handle(pin_obj, &ptr)) {
        gpio_handle_t *handle = (gpio_handle_t *)ptr;
        *port = handle->port;
        *pin = handle->pin;
    }
}

// C callback from task context in response to GPIO input interrupt
static void zjs_gpio_c_callback(void *h, const void *args)
{
    gpio_handle_t *handle = (gpio_handle_t *)h;
    if (handle->closed) {
        ERR_PRINT("unexpected callback after close");
        return;
    }
    ZVAL onchange_func = zjs_get_property(handle->pin_obj, "onchange");

    // If pin.onChange exists, call it
    if (jerry_value_is_function(onchange_func)) {
        ZVAL event = jerry_create_object();
        uint32_t *the_args = (uint32_t *)args;
        // Put the numeric GPIO trigger value in the object
        zjs_obj_add_number(event, the_args[0], "value");

        // Call the JS callback
        jerry_call_function(onchange_func, ZJS_UNDEFINED, &event, 1);
    } else {
        DBG_PRINT(("onChange has not been registered\n"));
    }
}

// Callback when a GPIO input fires
// INTERRUPT SAFE FUNCTION: No JerryScript VM, allocs, or release prints!
static void zjs_gpio_zephyr_callback(DEVICE port, struct gpio_callback *cb,
                                     uint32_t pins)
{
    // Get our handle for this pin
    gpio_handle_t *handle = CONTAINER_OF(cb, gpio_handle_t, callback);
    // Read the value and save it in the handle
    gpio_pin_read(port, handle->pin, &handle->value);
    if ((handle->edge_both && handle->value != handle->last) ||
        !handle->edge_both) {
        uint32_t args[] = {handle->value, pins};
        // Signal the C callback, where we call the JS callback
        zjs_signal_callback(handle->callbackId, args, sizeof(args));
        handle->last = handle->value;
    }
}

static ZJS_DECL_FUNC(zjs_gpio_pin_read)
{
    // requires: this is a GPIOPin object from zjs_gpio_open, takes no args
    //  effects: reads a logical value from the pin and returns it in ret_val_p
    uintptr_t ptr;
    gpio_handle_t *handle;
    if (!jerry_get_object_native_handle(this, &ptr)) {
        return SYSTEM_ERROR("no handle found");
    }

    handle = (gpio_handle_t *)ptr;
    if (handle->closed) {
        return zjs_error("pin closed");
    }

    uint32_t value;
    int rval = gpio_pin_read(handle->port, handle->pin, &value);
    if (rval) {
        ERR_PRINT("PIN: #%d\n", (int)handle->pin);
        return zjs_error("read failed");
    }

    if (handle->active_low) {
        value = !value;
    }

    return jerry_create_number(value);
}

static ZJS_DECL_FUNC(zjs_gpio_pin_write)
{
    // requires: this is a GPIOPin object from zjs_gpio_open, takes one arg,
    //             the logical boolean value to set to the pin (true = active)
    //  effects: writes the logical value to the pin

    // args: pin value
    ZJS_VALIDATE_ARGS(Z_BOOL Z_NUMBER);

    uintptr_t ptr;
    if (!jerry_get_object_native_handle(this, &ptr)) {
        return SYSTEM_ERROR("no handle found");
    }
    gpio_handle_t *handle = (gpio_handle_t *)ptr;
    if (handle->closed) {
        return zjs_error("pin closed");
    }

    uint32_t value;
    // TODO: Remove this deprecated option eventually
    if (jerry_value_is_boolean(argv[0])) {
        ZJS_PRINT("Deprecated! gpio.write() no longer takes a boolean!\n");
        value = jerry_get_boolean_value(argv[0]) ? 1 : 0;
    }
    else {
        value = (uint32_t)jerry_get_number_value(argv[0]);
    }

    if (handle->active_low) {
        value = !value;
    }

    int rval = gpio_pin_write(handle->port, handle->pin, value);
    if (rval) {
        ERR_PRINT("GPIO: #%d!n", (int)handle->pin);
        return zjs_error("write failed");
    }

    return ZJS_UNDEFINED;
}

static void zjs_gpio_close(gpio_handle_t *handle)
{
        zjs_remove_callback(handle->callbackId);
        gpio_remove_callback(handle->port, &handle->callback);
        handle->closed = true;
}

static ZJS_DECL_FUNC(zjs_gpio_pin_close)
{
    uintptr_t ptr;
    if (jerry_get_object_native_handle(this, &ptr)) {
        gpio_handle_t *handle = (gpio_handle_t *)ptr;
        if (handle->closed)
            return zjs_error("zjs_gpio_pin_close: already closed");

        zjs_gpio_close(handle);
        return ZJS_UNDEFINED;
    }

    return zjs_error("zjs_gpio_pin_close: no native handle");
}

static void zjs_gpio_free_cb(const uintptr_t native)
{
    gpio_handle_t *handle = (gpio_handle_t *)native;
    if (!handle->closed)
        zjs_gpio_close(handle);

    zjs_free(handle);
}

enum {
    ZJS_DIR_INPUT,
    ZJS_DIR_OUTPUT
};

enum {
    ZJS_EDGE_NONE,
    ZJS_EDGE_RISING = GPIO_INT | GPIO_INT_EDGE | GPIO_INT_ACTIVE_HIGH,
    ZJS_EDGE_FALLING = GPIO_INT | GPIO_INT_EDGE | GPIO_INT_ACTIVE_LOW,
    ZJS_EDGE_BOTH = GPIO_INT | GPIO_INT_EDGE | GPIO_INT_DOUBLE_EDGE
};

enum {
    ZJS_PULL_NONE,
    ZJS_PULL_UP,
    ZJS_PULL_DOWN
};

static ZJS_DECL_FUNC(zjs_gpio_open)
{
    // args: initialization object or int/string pin number
    ZJS_VALIDATE_ARGS(Z_NUMBER Z_STRING Z_OBJECT);

    ZVAL_MUTABLE pin_str = 0;
    jerry_value_t pin_val = argv[0];
    jerry_value_t init = 0;

    if (jerry_value_is_object(argv[0])) {
        init = argv[0];
        pin_str = zjs_get_property(init, "pin");
        pin_val = pin_str;
    }

    char devname[20];
    int pin;
    int rval = zjs_board_find_pin(pin_val, devname, &pin);
    if (rval == FIND_PIN_INVALID) {
        return TYPE_ERROR("bad pin argument");
    }
    DEVICE gpiodev = device_get_binding(devname);
    if (!gpiodev || rval == FIND_PIN_FAILURE) {
        return zjs_custom_error("InvalidAccessError", "pin failure");
    }

    // ignore mapping for now as we can do it automatically

    // set defaults
    bool active_low = false;
    int dir = ZJS_DIR_OUTPUT;
    int edge = ZJS_EDGE_NONE;
    int pull = ZJS_PULL_NONE;

    if (init) {
        // TODO: Remove warning eventually
        ZVAL dirprop = zjs_get_property(init, "direction");
        if (!jerry_value_is_undefined(dirprop)) {
            ZJS_PRINT("Deprecated! gpio.open() direction property is "
                      "now 'mode'\n");
        }

        // override defaults where specified
        ZJS_REQUIRE_BOOL_IF_PROP(init, "activeLow", &active_low);

        str2int_t mode_map[] = {
            {"in", ZJS_DIR_INPUT},
            {"out", ZJS_DIR_OUTPUT},
            {NULL, 0}
        };
        ZJS_REQUIRE_STR_IF_PROP_MAP(init, "mode", mode_map, 10, &dir);

        str2int_t edge_map[] = {
            {"none", ZJS_EDGE_NONE},
            {"rising", ZJS_EDGE_RISING},
            {"falling", ZJS_EDGE_FALLING},
            {"any", ZJS_EDGE_BOTH},
            {NULL, 0}
        };
        ZJS_REQUIRE_STR_IF_PROP_MAP(init, "edge", edge_map, 10, &edge);

        str2int_t pull_map[] = {
            {"none", ZJS_PULL_NONE},
            {"pullup", ZJS_PULL_UP},
            {"pulldown", ZJS_PULL_DOWN},
            {NULL, 0}
        };
        ZJS_REQUIRE_STR_IF_PROP_MAP(init, "state", pull_map, 10, &pull);
    }

    int flags = 0;
    flags |= active_low ? GPIO_POL_INV : GPIO_POL_NORMAL;
    if (dir == ZJS_DIR_INPUT) {
        flags |= GPIO_DIR_IN | edge;
    }
    else {
        flags |= GPIO_DIR_OUT;
    }
    if (pull == ZJS_PULL_NONE) {
       flags |= GPIO_PUD_NORMAL;
    }
    else {
        flags |= (pull == ZJS_PULL_UP) ? GPIO_PUD_PULL_UP : GPIO_PUD_PULL_DOWN;
    }

    rval = gpio_pin_configure(gpiodev, pin, flags);
    if (rval) {
        ERR_PRINT("GPIO: #%d (RVAL: %d)\n", (int)pin, rval);
        return zjs_error("pin configure failed");
    }

    // create the GPIOPin object
    ZVAL pin_obj = jerry_create_object();
    jerry_set_prototype(pin_obj, gpio_pin_prototype);

    gpio_handle_t *handle = zjs_malloc(sizeof(gpio_handle_t));
    memset(handle, 0, sizeof(gpio_handle_t));
    handle->pin = pin;
    handle->pin_obj = pin_obj;
    handle->port = gpiodev;
    handle->callbackId = -1;
    handle->active_low = active_low;

    jerry_set_object_native_handle(pin_obj, (uintptr_t)handle,
                                   zjs_gpio_free_cb);

    if (dir == ZJS_DIR_INPUT) {
        // Zephyr ISR callback init
        gpio_init_callback(&handle->callback, zjs_gpio_zephyr_callback,
                           BIT(pin));
        gpio_add_callback(gpiodev, &handle->callback);
        gpio_pin_enable_callback(gpiodev, pin);

        // Register a C callback (will be called after the ISR is called)
        handle->callbackId = zjs_add_c_callback(handle, zjs_gpio_c_callback);
        handle->edge_both = (edge == ZJS_EDGE_BOTH) ? 1 : 0;
    }

    return jerry_acquire_value(pin_obj);
}

jerry_value_t zjs_gpio_init()
{
    zjs_gpio_mock_pre_init();

    if (gpio_api) {
        return jerry_acquire_value(gpio_api);
    }

    // create GPIO pin prototype object
    zjs_native_func_t array[] = {
        { zjs_gpio_pin_read, "read" },
        { zjs_gpio_pin_write, "write" },
        { zjs_gpio_pin_close, "close" },
        { NULL, NULL }
    };
    gpio_pin_prototype = jerry_create_object();
    zjs_obj_add_functions(gpio_pin_prototype, array);

    // create GPIO object
    gpio_api = jerry_create_object();
    zjs_obj_add_function(gpio_api, zjs_gpio_open, "open");

    zjs_gpio_mock_post_init(gpio_api);

    return jerry_acquire_value(gpio_api);
}

void zjs_gpio_cleanup()
{
    zjs_gpio_mock_cleanup();
    jerry_release_value(gpio_pin_prototype);
    jerry_release_value(gpio_api);
    gpio_pin_prototype = 0;
    gpio_api = 0;
}