1/*
2 * Copyright 2019, Data61
3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
4 * ABN 41 687 119 230.
5 *
6 * This software may be distributed and modified according to the terms of
7 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
8 * See "LICENSE_BSD2.txt" for details.
9 *
10 * @TAG(DATA61_BSD)
11 */
12
13#include <stdbool.h>
14#include <stddef.h>
15#include <utils/util.h>
16#include <platsupport/gpio_utils.h>
17
18/* This is forward declared in the header file. */
19struct gpio_chain {
20    list_t pin_list;
21};
22
23struct gpio_callback_token {
24    bool is_read;
25    char *buffer;
26    size_t bits_processed;
27    size_t nbits_to_process;
28}
29
30static int gpio_chain_comparator(void *a, void *b)
31{
32    gpio_t *gpio_a = a;
33    gpio_t *gpio_b = b;
34
35    if (gpio_a->id == gpio_b->id) {
36        return 0;
37    }
38
39    return -1;
40}
41
42int gpio_chain_init(ps_malloc_ops_t *malloc_ops, gpio_chain_t **ret_chain)
43{
44    if (!malloc_ops || !ret_chain) {
45        ZF_LOGE("Arguments are NULL!");
46        return -EINVAL;
47    }
48
49    int error = ps_calloc(malloc_ops, 1, sizeof(**ret_chain), (void **) ret_chain);
50    if (error) {
51        return -ENOMEM;
52    }
53
54    error = list_init(&(*ret_chain)->pin_list);
55    if (error) {
56        ps_free(malloc_ops, sizeof(**ret_chain), *ret_chain);
57        return error;
58    }
59
60    return 0;
61}
62
63int gpio_chain_destroy(ps_malloc_ops_t *malloc_ops, gpio_chain_t *chain)
64{
65    if (!malloc_ops || !chain) {
66        ZF_LOGE("Arguments are NULL!");
67        return -EINVAL;
68    }
69
70    /* We're expected to have removed all elements from
71     * the list before destroying it */
72    int error = list_remove_all(&chain->pin_list);
73    if (error) {
74        return error;
75    }
76
77    error = list_destroy(&chain->pin_list);
78    if (error) {
79        return error;
80    }
81
82    ps_free(malloc_ops, sizeof(*chain), chain);
83
84    return 0;
85}
86
87int gpio_chain_add(gpio_chain_t *chain, gpio_t *gpio)
88{
89    if (!chain || !gpio) {
90        ZF_LOGE("Arguments are NULL!");
91        return -EINVAL;
92    }
93
94    int error = list_append(&chain->pin_list, (void *) gpio);
95    if (error) {
96        return error;
97    }
98
99    return 0;
100}
101
102int gpio_chain_remove(gpio_chain_t *chain, gpio_t *gpio)
103{
104    if (!chain || !gpio) {
105        ZF_LOGE("Arguments are NULL!");
106        return -EINVAL;
107    }
108
109    int not_found = list_remove(&chain->pin_list, gpio, gpio_chain_comparator);
110    if (not_found) {
111        return -ENOENT;
112    }
113    return 0;
114}
115
116int gpio_chain_io_callback(void *data, void *token)
117{
118    gpio_t *gpio = data;
119    struct gpio_callback_token *cb_token = token;
120
121    /* Do nothing if we've already processed enough bits */
122    if (cb_token->nbits_to_process == cb_token->bits_processed) {
123        return 0;
124    }
125
126    size_t curr_byte = cb_token->bits_processed / sizeof(*(cb_token->buffer));
127    size_t curr_bit = cb_token->bits_processed % sizeof(*(cb_token->buffer));
128
129    if (cb_token->is_read) {
130        int val = gpio_get(gpio);
131
132        val <<= curr_bit;
133        cb_token->buffer[curr_byte] &= ~BIT(curr_bit);
134        cb_token->buffer[curr_byte] |= val;
135    } else {
136        if (cb_token->buffer[curr_byte] & BIT(curr_bit)) {
137            gpio_set(gpio);
138        } else {
139            gpio_clr(gpio);
140        }
141    }
142
143    cb_token->bits_processed++;
144
145    return 0;
146}
147
148int gpio_chain_read(gpio_chain_t *chain, char *data, int len)
149{
150    if (!chain || !data) {
151        ZF_LOGE("Arguments are NULL!");
152        return -EINVAL;
153    }
154
155    struct gpio_callback_token cb_token = { .is_read = true, .buffer = data, .bits_processed = 0,
156               .nbits_to_process = len
157    };
158
159    int error = list_foreach(&chain->pin_list, gpio_chain_io_callback, &cb_token);
160    if (error) {
161        return error;
162    }
163
164    return cb_token->bits_processed;
165}
166
167int gpio_chain_write(gpio_chain_t *chain, const char *data, int len)
168{
169    if (!chain || !data) {
170        ZF_LOGE("Arguments are NULL!");
171        return -EINVAL;
172    }
173
174    struct gpio_callback_token cb_token = { .is_read = false, .buffer = data, .bits_processed = 0,
175               .nbits_to_process = len
176    };
177
178    int error = list_foreach(&chain->pin_list, gpio_chain_io_callback, &cb_token);
179    if (error) {
180        return error;
181    }
182
183    return cb_token->bits_processed;
184}
185