aboutsummaryrefslogtreecommitdiff
path: root/module/i2c/src/mod_i2c.c
blob: b310c008668f4210e282c33994c63138145c94d6 (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
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
/*
 * Arm SCP/MCP Software
 * Copyright (c) 2017-2020, Arm Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
#include <stdbool.h>
#include <string.h>
#include <fwk_assert.h>
#include <fwk_id.h>
#include <fwk_mm.h>
#include <fwk_module.h>
#include <fwk_module_idx.h>
#include <fwk_status.h>
#include <fwk_thread.h>
#include <mod_i2c.h>

enum mod_i2c_dev_state {
    MOD_I2C_DEV_IDLE,
    MOD_I2C_DEV_TX,
    MOD_I2C_DEV_RX,
    MOD_I2C_DEV_TX_RX,
};

struct mod_i2c_dev_ctx {
    const struct mod_i2c_dev_config *config;
    const struct mod_i2c_driver_api *driver_api;
    struct mod_i2c_request request;
    uint32_t cookie;
    enum mod_i2c_dev_state state;
};

static struct mod_i2c_dev_ctx *ctx_table;

enum mod_i2c_internal_event_idx {
    MOD_I2C_EVENT_IDX_REQUEST_COMPLETED = MOD_I2C_EVENT_IDX_COUNT,
    MOD_I2C_EVENT_IDX_TOTAL_COUNT,
};

/*! Request completed event identifier */
static const fwk_id_t mod_i2c_event_id_request_completed = FWK_ID_EVENT_INIT(
    FWK_MODULE_IDX_I2C, MOD_I2C_EVENT_IDX_REQUEST_COMPLETED);

/*
 * Static helpers
 */
static void get_ctx(fwk_id_t id, struct mod_i2c_dev_ctx **ctx)
{
    fwk_assert(fwk_module_is_valid_element_id(id));

    *ctx = ctx_table + fwk_id_get_element_idx(id);
}

static int create_i2c_request(fwk_id_t dev_id,
                              struct mod_i2c_request *request)
{
    int status;
    struct fwk_event event;
    struct mod_i2c_dev_ctx *ctx;
    struct mod_i2c_request *event_param =
        (struct mod_i2c_request *)event.params;

    get_ctx(dev_id, &ctx);

    /* The slave address should be on 7 bits */
    if (!fwk_expect(request->slave_address < 0x80))
        return FWK_E_PARAM;

    /* Check whether an I2C request is already on-going */
    if (ctx->state != MOD_I2C_DEV_IDLE)
        return FWK_E_BUSY;

    /* Create the request */
    event = (struct fwk_event) {
        .target_id = dev_id,
        .response_requested = true,
    };

    *event_param = *request;

    if ((request->transmit_byte_count > 0) &&
        (request->receive_byte_count > 0)) {
        ctx->state = MOD_I2C_DEV_TX_RX;
        event.id = mod_i2c_event_id_request_tx_rx;
    } else if (request->transmit_byte_count > 0) {
        ctx->state = MOD_I2C_DEV_TX;
        event.id = mod_i2c_event_id_request_tx;
    } else if (request->receive_byte_count > 0) {
        ctx->state = MOD_I2C_DEV_RX;
        event.id = mod_i2c_event_id_request_rx;
    }

    status = fwk_thread_put_event(&event);
    if (status == FWK_SUCCESS) {
        /*
         * The request has been successfully queued for later processing by the
         * I2C device but processing of this request has not yet begun. The
         * caller is notified that the I2C request is in progress.
         */
        return FWK_PENDING;
    }

    ctx->state = MOD_I2C_DEV_IDLE;

    return status;
}

/*
 * I2C API
 */
static int transmit_as_master(fwk_id_t dev_id,
                              uint8_t slave_address,
                              uint8_t *data,
                              uint8_t byte_count)
{
    if (!fwk_expect(byte_count != 0))
        return FWK_E_PARAM;

    if (!fwk_expect(data != NULL))
        return FWK_E_PARAM;

    struct mod_i2c_request request = {
        .slave_address = slave_address,
        .transmit_data = data,
        .transmit_byte_count = byte_count,
    };

    return create_i2c_request(dev_id, &request);
}

static int receive_as_master(fwk_id_t dev_id,
                             uint8_t slave_address,
                             uint8_t *data,
                             uint8_t byte_count)
{
    if (!fwk_expect(byte_count != 0))
        return FWK_E_PARAM;

    if (!fwk_expect(data != NULL))
        return FWK_E_PARAM;

    struct mod_i2c_request request = {
        .slave_address = slave_address,
        .receive_data = data,
        .receive_byte_count = byte_count,
    };

    return create_i2c_request(dev_id, &request);
}

static int transmit_then_receive_as_master(fwk_id_t dev_id,
                                           uint8_t slave_address,
                                           uint8_t *transmit_data,
                                           uint8_t *receive_data,
                                           uint8_t transmit_byte_count,
                                           uint8_t receive_byte_count)
{
    if (!fwk_expect((transmit_byte_count != 0) && (receive_byte_count != 0)))
        return FWK_E_PARAM;

    if (!fwk_expect((transmit_data != NULL) && (receive_data != NULL)))
        return FWK_E_PARAM;

    struct mod_i2c_request request = {
        .slave_address = slave_address,
        .transmit_data = transmit_data,
        .receive_data = receive_data,
        .transmit_byte_count = transmit_byte_count,
        .receive_byte_count = receive_byte_count,
    };

    return create_i2c_request(dev_id, &request);
}

static struct mod_i2c_api i2c_api = {
    .transmit_as_master = transmit_as_master,
    .receive_as_master = receive_as_master,
    .transmit_then_receive_as_master = transmit_then_receive_as_master,
};

/*
 * Driver response API
 */
static void transaction_completed(fwk_id_t dev_id, int i2c_status)
{
    int status;
    struct fwk_event event;
    struct mod_i2c_event_param* param =
        (struct mod_i2c_event_param *)event.params;

    event = (struct fwk_event) {
        .target_id = dev_id,
        .source_id = dev_id,
        .id = mod_i2c_event_id_request_completed,
    };

    param->status = i2c_status;

    status = fwk_thread_put_event(&event);
    fwk_assert(status == FWK_SUCCESS);
}

static struct mod_i2c_driver_response_api driver_response_api = {
    .transaction_completed = transaction_completed,
};

/*
 * Framework handlers
 */
static int mod_i2c_init(fwk_id_t module_id,
                        unsigned int element_count,
                        const void *unused)
{
    ctx_table = fwk_mm_calloc(element_count, sizeof(ctx_table[0]));

    if (ctx_table == NULL)
        return FWK_E_NOMEM;

    return FWK_SUCCESS;
}

static int mod_i2c_dev_init(fwk_id_t element_id,
                            unsigned int unused,
                            const void *data)
{
    struct mod_i2c_dev_ctx *ctx;

    ctx = ctx_table + fwk_id_get_element_idx(element_id);
    ctx->config = (struct mod_i2c_dev_config *)data;

    return FWK_SUCCESS;
}

static int mod_i2c_bind(fwk_id_t id, unsigned int round)
{
    int status;
    struct mod_i2c_dev_ctx *ctx;

    /*
     * Only bind in first round of calls
     * Nothing to do for module
     */
    if ((round > 0) || fwk_id_is_type(id, FWK_ID_TYPE_MODULE))
        return FWK_SUCCESS;

    ctx = ctx_table + fwk_id_get_element_idx(id);

    /* Bind to driver */
    status = fwk_module_bind(ctx->config->driver_id,
                             ctx->config->api_id,
                             &ctx->driver_api);
    if (status != FWK_SUCCESS)
        return status;

    if ((ctx->driver_api->transmit_as_master == NULL) ||
        (ctx->driver_api->receive_as_master == NULL))
        return FWK_E_DATA;

    return FWK_SUCCESS;
}

static int mod_i2c_process_bind_request(fwk_id_t source_id,
                                        fwk_id_t target_id,
                                        fwk_id_t api_id,
                                        const void **api)
{
    struct mod_i2c_dev_ctx *ctx;

    if (!fwk_id_is_type(target_id, FWK_ID_TYPE_ELEMENT))
        return FWK_E_PARAM;

    ctx = ctx_table + fwk_id_get_element_idx(target_id);

    if (fwk_id_is_equal(source_id, ctx->config->driver_id)) {
        if (fwk_id_is_equal(api_id, mod_i2c_api_id_driver_response))
            *api = &driver_response_api;
        else
            return FWK_E_PARAM;
    } else
        *api = &i2c_api;

    return FWK_SUCCESS;
}

static int respond_to_caller(
    fwk_id_t dev_id,
    struct mod_i2c_dev_ctx *ctx,
    int drv_status)
{
    int status;
    struct fwk_event resp;
    struct mod_i2c_event_param *param =
        (struct mod_i2c_event_param *)resp.params;

    ctx->state = MOD_I2C_DEV_IDLE;

    status = fwk_thread_get_delayed_response(dev_id, ctx->cookie, &resp);
    if (status != FWK_SUCCESS)
        return status;

    param->status = (drv_status == FWK_SUCCESS) ? FWK_SUCCESS : FWK_E_DEVICE;

    return fwk_thread_put_event(&resp);
}

static int process_request(struct mod_i2c_dev_ctx *ctx, fwk_id_t event_id)
{
    int drv_status;
    const struct mod_i2c_driver_api *driver_api = ctx->driver_api;
    fwk_id_t driver_id = ctx->config->driver_id;

    switch (fwk_id_get_event_idx(event_id)) {
    case MOD_I2C_EVENT_IDX_REQUEST_TRANSMIT:
        drv_status = driver_api->transmit_as_master(driver_id, &ctx->request);
        break;

    case MOD_I2C_EVENT_IDX_REQUEST_TRANSMIT_THEN_RECEIVE:
        drv_status = driver_api->transmit_as_master(driver_id, &ctx->request);

        if (drv_status != FWK_SUCCESS) {
            /* The request has failed or been acknowledged */
            break;
        } else {
            /*
             * The TX request has succeeded, update state and proceed to the RX
             * request.
             */
            ctx->state = MOD_I2C_DEV_RX;
        }
        /* fall through */

    case MOD_I2C_EVENT_IDX_REQUEST_RECEIVE:
        drv_status = driver_api->receive_as_master(driver_id, &ctx->request);
        break;
    }

    return drv_status;
}

static int mod_i2c_process_event(const struct fwk_event *event,
                                 struct fwk_event *resp_event)
{
    fwk_id_t dev_id;
    int status, drv_status;
    bool is_request;
    struct mod_i2c_dev_ctx *ctx;
    struct mod_i2c_request *request;
    struct mod_i2c_event_param *event_param, *resp_param;

    dev_id = event->target_id;
    get_ctx(dev_id, &ctx);

    is_request = fwk_id_get_event_idx(event->id) < MOD_I2C_EVENT_IDX_COUNT;

    if (is_request) {
        request = (struct mod_i2c_request *)event->params;
        ctx->request = *request;

        drv_status = process_request(ctx, event->id);

        if (drv_status == FWK_PENDING) {
            ctx->cookie = event->cookie;
            resp_event->is_delayed_response = true;
        } else {
            /* The request has succeeded or failed, respond now */
            resp_param = (struct mod_i2c_event_param *)resp_event->params;

            resp_param->status = (drv_status == FWK_SUCCESS) ?
                                 FWK_SUCCESS : FWK_E_DEVICE;
            ctx->state = MOD_I2C_DEV_IDLE;
        }

        return FWK_SUCCESS;
    }

    switch (fwk_id_get_event_idx(event->id)) {
    case MOD_I2C_EVENT_IDX_REQUEST_COMPLETED:
        event_param = (struct mod_i2c_event_param *)event->params;

        if ((ctx->state == MOD_I2C_DEV_TX) || (ctx->state == MOD_I2C_DEV_RX)) {
            status = respond_to_caller(dev_id, ctx, event_param->status);
        } else if (ctx->state == MOD_I2C_DEV_TX_RX) {
            drv_status = event_param->status;

            if (drv_status == FWK_SUCCESS) {
                /* The TX request succeeded, proceed with the RX */
                ctx->state = MOD_I2C_DEV_RX;

                drv_status = ctx->driver_api->receive_as_master(
                    ctx->config->driver_id,
                    &ctx->request);

                if (drv_status == FWK_PENDING) {
                    status = FWK_SUCCESS;
                    break;
                }
            }

            status = respond_to_caller(dev_id, ctx, drv_status);
        } else
            status = FWK_E_STATE;

        break;

    default:
        status = FWK_E_PANIC;
        break;
    }

    return status;
}

const struct fwk_module module_i2c = {
    .name = "I2C",
    .type = FWK_MODULE_TYPE_HAL,
    .api_count = MOD_I2C_API_IDX_COUNT,
    .event_count = MOD_I2C_EVENT_IDX_TOTAL_COUNT,
    .init = mod_i2c_init,
    .element_init = mod_i2c_dev_init,
    .bind = mod_i2c_bind,
    .process_bind_request = mod_i2c_process_bind_request,
    .process_event = mod_i2c_process_event,
};