Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- ESP32: Added support to SPI peripherals other than hspi and vspi
- Added `gpio:set_int/4`, with the 4th parameter being the pid() or registered name of the process to receive interrupt messages
- Added support for `lists:split/2`
- Added ESP32 API for allowing coexistence of native and Erlang I2C drivers

### Changed

Expand Down
81 changes: 76 additions & 5 deletions src/platforms/esp32/components/avm_builtins/i2c_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
#include "esp32_sys.h"
#include "sys.h"

#include "include/i2c_driver.h"

#define TAG "i2c_driver"

static void i2c_driver_init(GlobalContext *global);
Expand Down Expand Up @@ -89,6 +91,9 @@ struct I2CData
i2c_cmd_handle_t cmd;
term transmitting_pid;
i2c_port_t i2c_num;

// no need to make it atomic, we use it only when the process table is locked
int ref_count;
};

#define I2C_VALIDATE_NOT_INVALID(moniker) \
Expand All @@ -106,6 +111,7 @@ void i2c_driver_init(GlobalContext *global)
Context *i2c_driver_create_port(GlobalContext *global, term opts)
{
struct I2CData *i2c_data = calloc(1, sizeof(struct I2CData));
i2c_data->ref_count = 1;
i2c_data->transmitting_pid = term_invalid_term();

term scl_io_num_term = interop_kv_get_value(opts, ATOM_STR("\x3", "scl"), global);
Expand Down Expand Up @@ -165,16 +171,23 @@ Context *i2c_driver_create_port(GlobalContext *global, term opts)
return NULL;
}

static void i2c_driver_close(Context *ctx)
static NativeHandlerResult i2c_driver_maybe_close(Context *ctx)
{
struct I2CData *i2c_data = ctx->platform_data;
if (--i2c_data->ref_count != 0) {
return NativeContinue;
}

ctx->platform_data = NULL;

esp_err_t err = i2c_driver_delete(i2c_data->i2c_num);
if (UNLIKELY(err != ESP_OK)) {
ESP_LOGW(TAG, "Failed to delete I2C driver. err=%i", err);
}
free(ctx->platform_data);
ctx->platform_data = NULL;

free(i2c_data);

return NativeTerminate;
}

static term i2cdriver_begin_transmission(Context *ctx, term pid, term req)
Expand Down Expand Up @@ -564,6 +577,7 @@ static NativeHandlerResult i2cdriver_consume_mailbox(Context *ctx)
int local_process_id = term_to_local_process_id(gen_message.pid);

term ret;
NativeHandlerResult handler_result = NativeContinue;

enum i2c_cmd cmd = interop_atom_term_select_int(cmd_table, cmd_term, ctx->global);
switch (cmd) {
Expand Down Expand Up @@ -591,7 +605,11 @@ static NativeHandlerResult i2cdriver_consume_mailbox(Context *ctx)
}
break;
case I2CCloseCmd:
i2c_driver_close(ctx);
// ugly hack: we lock before closing so _release and _acquire can assume
// ctx->platform is not changed.
globalcontext_get_process_lock(ctx->global, ctx->process_id);
handler_result = i2c_driver_maybe_close(ctx);
globalcontext_get_process_unlock(ctx->global, ctx);
ret = OK_ATOM;
break;

Expand All @@ -610,7 +628,60 @@ static NativeHandlerResult i2cdriver_consume_mailbox(Context *ctx)
globalcontext_send_message(ctx->global, local_process_id, ret_msg);
mailbox_remove_message(&ctx->mailbox, &ctx->heap);

return cmd == I2CCloseCmd ? NativeTerminate : NativeContinue;
return handler_result;
}

I2CAcquireResult i2c_driver_acquire(term i2c_port, i2c_port_t *i2c_num, GlobalContext *global)
{
if (UNLIKELY(!term_is_pid(i2c_port))) {
ESP_LOGW(TAG, "Given term is not a I2C port driver.");
return I2CAcquireInvalidPeripheral;
}

int local_process_id = term_to_local_process_id(i2c_port);
Context *ctx = globalcontext_get_process_lock(global, local_process_id);

if ((ctx == NULL) || (ctx->native_handler != i2cdriver_consume_mailbox)
|| (ctx->platform_data == NULL)) {
ESP_LOGW(TAG, "Given term is not a I2C port driver.");
globalcontext_get_process_unlock(global, ctx);
return I2CAcquireInvalidPeripheral;
}

struct I2CData *i2c_data = ctx->platform_data;
i2c_data->ref_count++;

*i2c_num = i2c_data->i2c_num;

globalcontext_get_process_unlock(global, ctx);

return I2CAcquireOk;
}

void i2c_driver_release(term i2c_port, GlobalContext *global)
{
if (UNLIKELY(!term_is_pid(i2c_port))) {
ESP_LOGW(TAG, "Given term is not a I2C port driver.");
return;
}

int local_process_id = term_to_local_process_id(i2c_port);
Context *ctx = globalcontext_get_process_lock(global, local_process_id);

if ((ctx == NULL) || (ctx->native_handler != i2cdriver_consume_mailbox)
|| (ctx->platform_data == NULL)) {
ESP_LOGW(TAG, "Given term is not a I2C port driver.");
globalcontext_get_process_unlock(global, ctx);
return;
}

struct I2CData *i2c_data = ctx->platform_data;
i2c_data->ref_count--;
NativeHandlerResult close_result = i2c_driver_maybe_close(ctx);
if (close_result == NativeTerminate) {
mailbox_send_term_signal(ctx, KillSignal, NORMAL_ATOM);
}
globalcontext_get_process_unlock(global, ctx);
}

//
Expand Down
50 changes: 50 additions & 0 deletions src/platforms/esp32/components/avm_builtins/include/i2c_driver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* This file is part of AtomVM.
*
* Copyright 2024 Davide Bettio <davide@uninstall.it>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
*/

#ifndef _I2C_DRIVER_H_
#define _I2C_DRIVER_H_

#include <driver/i2c.h>

#include <globalcontext.h>
#include <term.h>

#define ATOMVM_ESP32_I2C_OLD_API 1

enum I2CAcquireOpts
{
I2CAcquireNoOpts
};

enum I2CAcquireResult
{
I2CAcquireOk,
I2CAcquireInvalidPeripheral
};

typedef enum I2CAcquireResult I2CAcquireResult;

// These functions are meant for integrating native drivers with the I2C port driver
// defined as following only when ATOMVM_ESP32_I2C_OLD_API is set
// it will be changed in future.
I2CAcquireResult i2c_driver_acquire(term i2c_port, i2c_port_t *i2c_num, GlobalContext *global);
void i2c_driver_release(term i2c_port, GlobalContext *global);

#endif