Current section

Files

Jump to
circuits_i2c src i2c_nif.c
Raw

src/i2c_nif.c

/*
* Copyright 2018 Frank Hunleth, Mark Sebald
*
* 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.
*
* I2C NIF implementation.
*/
#include "i2c_nif.h"
#include <errno.h>
#include <string.h>
// I2C NIF Resource.
struct I2cNifRes {
int fd;
};
// I2C NIF Private data
struct I2cNifPriv {
ErlNifResourceType *i2c_nif_res_type;
ERL_NIF_TERM atom_ok;
ERL_NIF_TERM atom_error;
ERL_NIF_TERM atom_nak;
};
static int i2c_load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM info)
{
#ifdef DEBUG
#ifdef LOG_PATH
log_location = fopen(LOG_PATH, "w");
#endif
#endif
debug("i2c_load");
struct I2cNifPriv *priv = enif_alloc(sizeof(struct I2cNifPriv));
if (!priv) {
error("Can't allocate i2c priv");
return 1;
}
priv->i2c_nif_res_type = enif_open_resource_type(env, NULL, "i2c_nif_res_type", NULL, ERL_NIF_RT_CREATE, NULL);
if (priv->i2c_nif_res_type == NULL) {
error("open I2C NIF resource type failed");
return 1;
}
priv->atom_ok = enif_make_atom(env, "ok");
priv->atom_error = enif_make_atom(env, "error");
priv->atom_nak = enif_make_atom(env, "i2c_nak");
*priv_data = priv;
return 0;
}
static void i2c_unload(ErlNifEnv *env, void *priv_data)
{
debug("i2c_unload");
struct I2cNifPriv *priv = enif_priv_data(env);
enif_free(priv);
}
static ERL_NIF_TERM enif_make_errno_error(ErlNifEnv *env)
{
struct I2cNifPriv *priv = enif_priv_data(env);
ERL_NIF_TERM reason;
switch (errno) {
#ifdef EREMOTEIO
case EREMOTEIO:
// Remote I/O errors are I2C naks
reason = priv->atom_nak;
break;
#endif
default:
// strerror isn't usually that helpful, so if these
// errors happen, please report or update this code
// to provide a better reason.
reason = enif_make_atom(env, strerror(errno));
break;
}
return enif_make_tuple2(env, priv->atom_error, reason);
}
static ERL_NIF_TERM i2c_open(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct I2cNifPriv *priv = enif_priv_data(env);
char device[16];
if (!enif_get_string(env, argv[0], device, sizeof(device), ERL_NIF_LATIN1))
return enif_make_badarg(env);
int fd = hal_i2c_open(device);
if (fd < 0)
return enif_make_errno_error(env);
struct I2cNifRes *i2c_nif_res = enif_alloc_resource(priv->i2c_nif_res_type, sizeof(struct I2cNifRes));
i2c_nif_res->fd = fd;
ERL_NIF_TERM res_term = enif_make_resource(env, i2c_nif_res);
// Elixir side owns the resource. Safe for NIF side to release it.
enif_release_resource(i2c_nif_res);
return enif_make_tuple2(env, priv->atom_ok, res_term);
}
static ERL_NIF_TERM i2c_read(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct I2cNifPriv *priv = enif_priv_data(env);
struct I2cNifRes *res;
unsigned int addr;
unsigned long read_len;
uint8_t read_data[I2C_BUFFER_MAX];
ErlNifBinary bin_read;
if (!enif_get_resource(env, argv[0], priv->i2c_nif_res_type, (void **)&res))
return enif_make_badarg(env);
if (!enif_get_uint(env, argv[1], &addr))
return enif_make_badarg(env);
if (!enif_get_ulong(env, argv[2], &read_len))
return enif_make_badarg(env);
if (hal_i2c_transfer(res->fd, addr, 0, 0, read_data, read_len) >= 0) {
bin_read.data = read_data;
bin_read.size = read_len;
return enif_make_tuple2(env, priv->atom_ok, enif_make_binary(env, &bin_read));
}
else
return enif_make_errno_error(env);
}
static ERL_NIF_TERM i2c_write(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct I2cNifPriv *priv = enif_priv_data(env);
struct I2cNifRes *res;
unsigned int addr;
ErlNifBinary bin_write;
if (!enif_get_resource(env, argv[0], priv->i2c_nif_res_type, (void **)&res))
return enif_make_badarg(env);
if (!enif_get_uint(env, argv[1], &addr))
return enif_make_badarg(env);
if (!enif_inspect_binary(env, argv[2], &bin_write))
return enif_make_badarg(env);
if (hal_i2c_transfer(res->fd, addr, bin_write.data, bin_write.size, 0, 0) >= 0)
return priv->atom_ok;
else
return enif_make_errno_error(env);
}
static ERL_NIF_TERM i2c_write_read(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct I2cNifPriv *priv = enif_priv_data(env);
struct I2cNifRes *res;
unsigned int addr;
ErlNifBinary bin_write;
uint8_t read_data[I2C_BUFFER_MAX];
unsigned long read_len;
ErlNifBinary bin_read;
if (!enif_get_resource(env, argv[0], priv->i2c_nif_res_type, (void **)&res))
return enif_make_badarg(env);
if (!enif_get_uint(env, argv[1], &addr))
return enif_make_badarg(env);
if (!enif_inspect_binary(env, argv[2], &bin_write))
return enif_make_badarg(env);
if (!enif_get_ulong(env, argv[3], &read_len))
return enif_make_badarg(env);
if (hal_i2c_transfer(res->fd, addr, bin_write.data, bin_write.size, read_data, read_len) >= 0) {
bin_read.data = read_data;
bin_read.size = read_len;
return enif_make_tuple2(env, priv->atom_ok, enif_make_binary(env, &bin_read));
}
else
return enif_make_errno_error(env);
}
static ERL_NIF_TERM i2c_close(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
struct I2cNifPriv *priv = enif_priv_data(env);
struct I2cNifRes *res;
if (!enif_get_resource(env, argv[0], priv->i2c_nif_res_type, (void **)&res))
return enif_make_badarg(env);
if (res->fd >= 0) {
hal_i2c_close(res->fd);
res->fd = -1;
}
return priv->atom_ok;
}
static ERL_NIF_TERM i2c_info(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
return hal_info(env);
}
static ErlNifFunc nif_funcs[] =
{
{"open", 1, i2c_open, ERL_NIF_DIRTY_JOB_IO_BOUND},
{"read", 3, i2c_read, 0},
{"write", 3, i2c_write, 0},
{"write_read", 4, i2c_write_read, 0},
{"close", 1, i2c_close, 0},
{"info", 0, i2c_info, 0}
};
ERL_NIF_INIT(Elixir.Circuits.I2C.Nif, nif_funcs, i2c_load, NULL, NULL, i2c_unload)