Add asynchronous I2C bus API
The driver core is based off of the i.MX233 I2C implementation and should work on any platform. Change-Id: I3b9c15e12a689ef02a51c285be08d29d35e323dc
This commit is contained in:
parent
eedc8934a9
commit
94b40ed314
3 changed files with 704 additions and 0 deletions
|
@ -1965,6 +1965,10 @@ target/hosted/sdl/filesystem-sdl.c
|
|||
drivers/touchpad.c
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_I2C_ASYNC
|
||||
drivers/i2c-async.c
|
||||
#endif
|
||||
|
||||
/* firmware/kernel section */
|
||||
#ifdef HAVE_CORELOCK_OBJECT
|
||||
kernel/corelock.c
|
||||
|
|
398
firmware/drivers/i2c-async.c
Normal file
398
firmware/drivers/i2c-async.c
Normal file
|
@ -0,0 +1,398 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2021 Aidan MacDonald
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include "i2c-async.h"
|
||||
#include "config.h"
|
||||
#include "system.h"
|
||||
#include "kernel.h"
|
||||
|
||||
/* To decide on the queue size, typically you should use the formula:
|
||||
*
|
||||
* queue size = max { D_1, ..., D_n } * max { T_1, ..., T_m }
|
||||
*
|
||||
* where
|
||||
*
|
||||
* n = number of busses
|
||||
* m = number of devices across all busses
|
||||
* D_i = number of devices on bus i
|
||||
* T_j = number of queued transactions needed for device j
|
||||
*
|
||||
* The idea is to ensure nobody waits for a queue slot, but keep the queue
|
||||
* size to a minimum so that queue management features don't take too long.
|
||||
* (They have to iterate over the entire queue with IRQs disabled, so...)
|
||||
*
|
||||
* If you don't use the asychronous features then waiting for a queue slot
|
||||
* is a moot point since you'll be blocking until completion anyway; in that
|
||||
* case feel free to use single-entry queues.
|
||||
*
|
||||
* Note that each bus gets the same sized queue. If this isn't good for
|
||||
* your target for whatever reason, it might be worth implementing per-bus
|
||||
* queue sizes here.
|
||||
*/
|
||||
#if !defined(I2C_ASYNC_BUS_COUNT) || !defined(I2C_ASYNC_QUEUE_SIZE)
|
||||
# error "Need to add #defines for i2c-async frontend"
|
||||
#endif
|
||||
|
||||
#if I2C_ASYNC_QUEUE_SIZE < 1
|
||||
# error "i2c-async queue size must be >= 1"
|
||||
#endif
|
||||
|
||||
/* Singly-linked list for queuing up transactions */
|
||||
typedef struct i2c_async_txn {
|
||||
struct i2c_async_txn* next;
|
||||
i2c_descriptor* desc;
|
||||
int cookie;
|
||||
} i2c_async_txn;
|
||||
|
||||
typedef struct i2c_async_bus {
|
||||
/* Head/tail of transaction queue. The head always points to the
|
||||
* currently running transaction, or NULL if the bus is idle.
|
||||
*/
|
||||
i2c_async_txn* head;
|
||||
i2c_async_txn* tail;
|
||||
|
||||
/* Head of a free list used to allocate nodes. */
|
||||
i2c_async_txn* free;
|
||||
|
||||
/* Next unallocated cookie value */
|
||||
int nextcookie;
|
||||
|
||||
/* Semaphore for reserving a node in the free list. Nodes can only be
|
||||
* allocated after a successful semaphore_wait(), and after being freed
|
||||
* semaphore_release() must be called.
|
||||
*/
|
||||
struct semaphore sema;
|
||||
|
||||
#ifdef HAVE_CORELOCK_OBJECT
|
||||
/* Corelock is required for multi-core CPUs */
|
||||
struct corelock cl;
|
||||
#endif
|
||||
} i2c_async_bus;
|
||||
|
||||
static i2c_async_txn i2c_async_txns[I2C_ASYNC_BUS_COUNT][I2C_ASYNC_QUEUE_SIZE];
|
||||
static i2c_async_bus i2c_async_busses[I2C_ASYNC_BUS_COUNT];
|
||||
|
||||
static i2c_async_txn* i2c_async_popfree(i2c_async_txn** head)
|
||||
{
|
||||
i2c_async_txn* node = *head;
|
||||
*head = node->next;
|
||||
return node;
|
||||
}
|
||||
|
||||
static void i2c_async_pushfree(i2c_async_txn** head, i2c_async_txn* node)
|
||||
{
|
||||
node->next = *head;
|
||||
*head = node;
|
||||
}
|
||||
|
||||
static i2c_async_txn* i2c_async_pool_init(i2c_async_txn* array, int count)
|
||||
{
|
||||
/* Populate forward pointers */
|
||||
for(int i = 0; i < count - 1; ++i)
|
||||
array[i].next = &array[i+1];
|
||||
|
||||
/* Last pointer is NULL */
|
||||
array[count - 1].next = NULL;
|
||||
|
||||
/* Return head of pool */
|
||||
return &array[0];
|
||||
}
|
||||
|
||||
static void i2c_async_bus_init(int busnr)
|
||||
{
|
||||
const int q_size = I2C_ASYNC_QUEUE_SIZE;
|
||||
|
||||
i2c_async_bus* bus = &i2c_async_busses[busnr];
|
||||
bus->head = bus->tail = NULL;
|
||||
bus->free = i2c_async_pool_init(i2c_async_txns[busnr], q_size);
|
||||
bus->nextcookie = 1;
|
||||
semaphore_init(&bus->sema, q_size, q_size);
|
||||
corelock_init(&bus->cl);
|
||||
}
|
||||
|
||||
/* Add a node to the end of the transaction queue */
|
||||
static void i2c_async_bus_enqueue(i2c_async_bus* bus, i2c_async_txn* node)
|
||||
{
|
||||
node->next = NULL;
|
||||
if(bus->head == NULL)
|
||||
bus->head = bus->tail = node;
|
||||
else {
|
||||
bus->tail->next = node;
|
||||
bus->tail = node;
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper function called to run descriptor completion tasks */
|
||||
static void i2c_async_bus_complete_desc(i2c_async_bus* bus, int status)
|
||||
{
|
||||
i2c_descriptor* d = bus->head->desc;
|
||||
if(d->callback)
|
||||
d->callback(status, d);
|
||||
|
||||
bus->head->desc = d->next;
|
||||
}
|
||||
|
||||
void __i2c_async_complete_callback(int busnr, int status)
|
||||
{
|
||||
i2c_async_bus* bus = &i2c_async_busses[busnr];
|
||||
corelock_lock(&bus->cl);
|
||||
|
||||
i2c_async_bus_complete_desc(bus, status);
|
||||
if(status != I2C_STATUS_OK) {
|
||||
/* Skip remainder of transaction after an error */
|
||||
while(bus->head->desc)
|
||||
i2c_async_bus_complete_desc(bus, I2C_STATUS_SKIPPED);
|
||||
}
|
||||
|
||||
/* Dequeue next transaction if we finished the current one */
|
||||
if(!bus->head->desc) {
|
||||
i2c_async_txn* node = bus->head;
|
||||
bus->head = node->next;
|
||||
i2c_async_pushfree(&bus->free, node);
|
||||
semaphore_release(&bus->sema);
|
||||
}
|
||||
|
||||
if(bus->head) {
|
||||
/* Submit the next descriptor */
|
||||
__i2c_async_submit(busnr, bus->head->desc);
|
||||
} else {
|
||||
/* Fixup tail after last transaction */
|
||||
bus->tail = NULL;
|
||||
}
|
||||
|
||||
corelock_unlock(&bus->cl);
|
||||
}
|
||||
|
||||
void __i2c_async_init(void)
|
||||
{
|
||||
for(int i = 0; i < I2C_ASYNC_BUS_COUNT; ++i)
|
||||
i2c_async_bus_init(i);
|
||||
}
|
||||
|
||||
int i2c_async_queue(int busnr, int timeout, int q_mode,
|
||||
int cookie, i2c_descriptor* desc)
|
||||
{
|
||||
i2c_async_txn* node;
|
||||
i2c_async_bus* bus = &i2c_async_busses[busnr];
|
||||
int rc = I2C_RC_OK;
|
||||
|
||||
int irq = disable_irq_save();
|
||||
corelock_lock(&bus->cl);
|
||||
|
||||
/* Scan the queue unless q_mode is a simple ADD */
|
||||
if(q_mode != I2C_Q_ADD) {
|
||||
for(node = bus->head; node != NULL; node = node->next) {
|
||||
if(node->cookie == cookie) {
|
||||
if(q_mode == I2C_Q_REPLACE && node != bus->head)
|
||||
node->desc = desc;
|
||||
else
|
||||
rc = I2C_RC_NOTADDED;
|
||||
goto _exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to claim a queue node without blocking if we can */
|
||||
if(semaphore_wait(&bus->sema, TIMEOUT_NOBLOCK) != OBJ_WAIT_SUCCEEDED) {
|
||||
/* Bail out now if caller doesn't want to wait */
|
||||
if(timeout == TIMEOUT_NOBLOCK) {
|
||||
rc = I2C_RC_BUSY;
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
/* Wait on the semaphore */
|
||||
corelock_unlock(&bus->cl);
|
||||
restore_irq(irq);
|
||||
if(semaphore_wait(&bus->sema, timeout) == OBJ_WAIT_TIMEDOUT)
|
||||
return I2C_RC_BUSY;
|
||||
|
||||
/* Got a node; re-lock */
|
||||
irq = disable_irq_save();
|
||||
corelock_lock(&bus->cl);
|
||||
}
|
||||
|
||||
/* Alloc the node and push it to queue */
|
||||
node = i2c_async_popfree(&bus->free);
|
||||
node->desc = desc;
|
||||
node->cookie = cookie;
|
||||
i2c_async_bus_enqueue(bus, node);
|
||||
|
||||
/* Start the first descriptor if the bus is idle */
|
||||
if(node == bus->head)
|
||||
__i2c_async_submit(busnr, desc);
|
||||
|
||||
_exit:
|
||||
corelock_unlock(&bus->cl);
|
||||
restore_irq(irq);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int i2c_async_cancel(int busnr, int cookie)
|
||||
{
|
||||
i2c_async_bus* bus = &i2c_async_busses[busnr];
|
||||
int rc = I2C_RC_NOTFOUND;
|
||||
|
||||
int irq = disable_irq_save();
|
||||
corelock_lock(&bus->cl);
|
||||
|
||||
/* Bail if queue is empty */
|
||||
if(!bus->head)
|
||||
goto _exit;
|
||||
|
||||
/* Check the running transaction for a match */
|
||||
if(bus->head->cookie == cookie) {
|
||||
rc = I2C_RC_BUSY;
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
/* Walk the queue, starting after the head */
|
||||
i2c_async_txn* prev = bus->head;
|
||||
i2c_async_txn* node = prev->next;
|
||||
while(node) {
|
||||
if(node->cookie == cookie) {
|
||||
prev->next = node->next;
|
||||
rc = I2C_RC_OK;
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
prev = node;
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
_exit:
|
||||
corelock_unlock(&bus->cl);
|
||||
restore_irq(irq);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int i2c_async_reserve_cookies(int busnr, int count)
|
||||
{
|
||||
i2c_async_bus* bus = &i2c_async_busses[busnr];
|
||||
int ret = bus->nextcookie;
|
||||
bus->nextcookie += count;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void i2c_sync_callback(int status, i2c_descriptor* d)
|
||||
{
|
||||
struct semaphore* sem = (struct semaphore*)d->arg;
|
||||
d->arg = status;
|
||||
semaphore_release(sem);
|
||||
}
|
||||
|
||||
static void i2c_reg_modify1_callback(int status, i2c_descriptor* d)
|
||||
{
|
||||
if(status == I2C_STATUS_OK) {
|
||||
uint8_t* buf = (uint8_t*)d->buffer[1];
|
||||
uint8_t val = *buf;
|
||||
*buf = (val & ~(d->arg >> 8)) | (d->arg & 0xff);
|
||||
d->arg = val;
|
||||
}
|
||||
}
|
||||
|
||||
int i2c_reg_write(int bus, uint8_t addr, uint8_t reg,
|
||||
int count, const uint8_t* buf)
|
||||
{
|
||||
struct semaphore sem;
|
||||
semaphore_init(&sem, 1, 0);
|
||||
|
||||
i2c_descriptor desc;
|
||||
desc.slave_addr = addr;
|
||||
desc.bus_cond = I2C_START | I2C_STOP;
|
||||
desc.tran_mode = I2C_WRITE;
|
||||
desc.buffer[0] = ®
|
||||
desc.count[0] = 1;
|
||||
desc.buffer[1] = (uint8_t*)buf;
|
||||
desc.count[1] = count;
|
||||
desc.callback = &i2c_sync_callback;
|
||||
desc.arg = (intptr_t)&sem;
|
||||
desc.next = NULL;
|
||||
|
||||
i2c_async_queue(bus, TIMEOUT_BLOCK, I2C_Q_ADD, 0, &desc);
|
||||
semaphore_wait(&sem, TIMEOUT_BLOCK);
|
||||
|
||||
return desc.arg;
|
||||
}
|
||||
|
||||
int i2c_reg_read(int bus, uint8_t addr, uint8_t reg,
|
||||
int count, uint8_t* buf)
|
||||
{
|
||||
struct semaphore sem;
|
||||
semaphore_init(&sem, 1, 0);
|
||||
|
||||
i2c_descriptor desc;
|
||||
desc.slave_addr = addr;
|
||||
desc.bus_cond = I2C_START | I2C_STOP;
|
||||
desc.tran_mode = I2C_READ;
|
||||
desc.buffer[0] = ®
|
||||
desc.count[0] = 1;
|
||||
desc.buffer[1] = buf;
|
||||
desc.count[1] = count;
|
||||
desc.callback = &i2c_sync_callback;
|
||||
desc.arg = (intptr_t)&sem;
|
||||
desc.next = NULL;
|
||||
|
||||
i2c_async_queue(bus, TIMEOUT_BLOCK, I2C_Q_ADD, 0, &desc);
|
||||
semaphore_wait(&sem, TIMEOUT_BLOCK);
|
||||
|
||||
return desc.arg;
|
||||
}
|
||||
|
||||
int i2c_reg_modify1(int bus, uint8_t addr, uint8_t reg,
|
||||
uint8_t clr, uint8_t set, uint8_t* val)
|
||||
{
|
||||
struct semaphore sem;
|
||||
semaphore_init(&sem, 1, 0);
|
||||
|
||||
uint8_t buf[2];
|
||||
buf[0] = reg;
|
||||
|
||||
i2c_descriptor desc[2];
|
||||
desc[0].slave_addr = addr;
|
||||
desc[0].bus_cond = I2C_START | I2C_STOP;
|
||||
desc[0].tran_mode = I2C_READ;
|
||||
desc[0].buffer[0] = &buf[0];
|
||||
desc[0].count[0] = 1;
|
||||
desc[0].buffer[1] = &buf[1];
|
||||
desc[0].count[1] = 1;
|
||||
desc[0].callback = &i2c_reg_modify1_callback;
|
||||
desc[0].arg = (clr << 8) | set;
|
||||
desc[0].next = &desc[1];
|
||||
|
||||
desc[1].slave_addr = addr;
|
||||
desc[1].bus_cond = I2C_START | I2C_STOP;
|
||||
desc[1].tran_mode = I2C_WRITE;
|
||||
desc[1].buffer[0] = &buf[0];
|
||||
desc[1].count[0] = 2;
|
||||
desc[1].buffer[1] = NULL;
|
||||
desc[1].count[1] = 0;
|
||||
desc[1].callback = &i2c_sync_callback;
|
||||
desc[1].arg = (intptr_t)&sem;
|
||||
desc[1].next = NULL;
|
||||
|
||||
i2c_async_queue(bus, TIMEOUT_BLOCK, I2C_Q_ADD, 0, &desc[0]);
|
||||
semaphore_wait(&sem, TIMEOUT_BLOCK);
|
||||
|
||||
if(val && desc[1].arg == I2C_STATUS_OK)
|
||||
*val = desc[0].arg;
|
||||
|
||||
return desc[1].arg;
|
||||
}
|
302
firmware/export/i2c-async.h
Normal file
302
firmware/export/i2c-async.h
Normal file
|
@ -0,0 +1,302 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2021 Aidan MacDonald
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef __I2C_ASYNC_H__
|
||||
#define __I2C_ASYNC_H__
|
||||
|
||||
#include "config.h"
|
||||
|
||||
/* i2c-async provides an API for asynchronous communication over an I2C bus.
|
||||
* It's not linked to a specific target, so device drivers using this API can
|
||||
* be shared more easily among multiple targets.
|
||||
*
|
||||
* Transactions are built using descriptors, and callbacks can be used to
|
||||
* perform work directly from interrupt context. Callbacks can even change
|
||||
* the descriptor chain on the fly, so the transaction can be altered based
|
||||
* on data recieved over the I2C bus.
|
||||
*
|
||||
* There's an API for synchronous operations on devices using 8-bit register
|
||||
* addresses. This API demonstrates how you can build more specialized routines
|
||||
* on top of the asynchronous API, and is useful in its own right for dealing
|
||||
* with simple devices.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_I2C_ASYNC
|
||||
|
||||
#include "i2c-target.h"
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* Queueing codes */
|
||||
#define I2C_RC_OK 0
|
||||
#define I2C_RC_BUSY 1
|
||||
#define I2C_RC_NOTADDED 2
|
||||
#define I2C_RC_NOTFOUND 3
|
||||
|
||||
/* Descriptor status codes */
|
||||
#define I2C_STATUS_OK 0
|
||||
#define I2C_STATUS_ERROR 1
|
||||
#define I2C_STATUS_TIMEOUT 2
|
||||
#define I2C_STATUS_SKIPPED 3
|
||||
|
||||
/* I2C bus end conditions */
|
||||
#define I2C_START (1 << 0)
|
||||
#define I2C_RESTART (1 << 1)
|
||||
#define I2C_CONTINUE (1 << 2)
|
||||
#define I2C_STOP (1 << 3)
|
||||
#define I2C_HOLD (1 << 4)
|
||||
|
||||
/* Transfer modes */
|
||||
#define I2C_READ 0
|
||||
#define I2C_WRITE 1
|
||||
|
||||
/* Queue modes */
|
||||
#define I2C_Q_ADD 0
|
||||
#define I2C_Q_ONCE 1
|
||||
#define I2C_Q_REPLACE 2
|
||||
|
||||
/* Flag for using 10-bit addresses */
|
||||
#define I2C_10BIT_ADDR 0x8000
|
||||
|
||||
/* Descriptors are used to set up an I2C transfer. The transfer mode is
|
||||
* specified by 'tran_mode', and is normally a READ or WRITE operation.
|
||||
* The transfer mode dictates how the buffer/count fields are interpreted.
|
||||
*
|
||||
* - I2C_WRITE
|
||||
* buffer[0] must be non-NULL and count[0] must be at least 1.
|
||||
* The transfer sends count[0] bytes from buffer[0] on the bus.
|
||||
*
|
||||
* buffer[1] and count[1] can be used to specify a second buffer
|
||||
* whose contents are written after the buffer[0]'s last byte.
|
||||
* No start/stop conditions are issued between the buffers; it is
|
||||
* as if you had appended the contents of buffer[1] to buffer[0].
|
||||
*
|
||||
* If not used, buffer[1] must be NULL and count[1] must be 0.
|
||||
* If used, buffer[1] must be non-NULL and count[1] must be >= 1.
|
||||
*
|
||||
* - I2C_READ
|
||||
* buffer[1] must be non-NULL and count[1] must be at least 1.
|
||||
* The transfer will request count[1] bytes and writes the data
|
||||
* into buffer[1].
|
||||
*
|
||||
* This type of transfer can send some data bytes before the
|
||||
* read operation, eg. to send a register address for the read.
|
||||
* If this feature is not used, set buffer[0] to NULL and set
|
||||
* count[0] to zero.
|
||||
*
|
||||
* If used, buffer[0] must be non-NULL and count[0] must be >= 1.
|
||||
* Between the last byte written and the first byte read, the bus
|
||||
* will automatically issue a RESTART condition.
|
||||
*
|
||||
* Bus conditions are divided into two classes: start and end conditions.
|
||||
* You MUST supply both a start and end condition in the 'bus_cond' field,
|
||||
* by OR'ing together one start condition and one end condition:
|
||||
*
|
||||
* - I2C_START
|
||||
* Issue a START condition before the first data byte. This must be
|
||||
* specified on the first descriptor of a transaction.
|
||||
*
|
||||
* - I2C_RESTART
|
||||
* Issue a RESTART condition before the first data byte. On the bus this
|
||||
* is physically identical to a START condition, but drivers might need
|
||||
* this to distinguish between these two cases.
|
||||
*
|
||||
* - I2C_CONTINUE
|
||||
* Do not issue any condition before the first data byte. This is only
|
||||
* valid if the descriptor continues a previous descriptor which ended
|
||||
* with the HOLD condition; otherwise the results are undefined.
|
||||
*
|
||||
* - I2C_STOP
|
||||
* Issue a STOP condition after the last data byte. This must be set on
|
||||
* the final descriptor in a transaction, so the bus is left in a usable
|
||||
* state when the descriptor finishes.
|
||||
*
|
||||
* - I2C_HOLD
|
||||
* Do not issue any condition after the last data byte. This is only
|
||||
* valid if the next descriptor starts with an I2C_CONTINUE condition.
|
||||
*/
|
||||
typedef struct i2c_descriptor {
|
||||
/* Address of the target device. To use 10-bit addresses, simply
|
||||
* OR the address with the I2C_10BIT_ADDR. */
|
||||
uint16_t slave_addr;
|
||||
|
||||
/* What to do at the ends of the data transfer */
|
||||
uint8_t bus_cond;
|
||||
|
||||
/* Transfer mode */
|
||||
uint8_t tran_mode;
|
||||
|
||||
/* Buffer/length fields. Their use depends on the transfer mode. */
|
||||
void* buffer[2];
|
||||
int count[2];
|
||||
|
||||
/* Callback which is invoked when the descriptor completes.
|
||||
*
|
||||
* The first argument is a status code and the second argument is a
|
||||
* pointer to the completed descriptor. The status code is the only
|
||||
* way of checking whether the descriptor completed successfully,
|
||||
* and it is NOT saved, so ensure you save it yourself if needed.
|
||||
*/
|
||||
void(*callback)(int, struct i2c_descriptor*);
|
||||
|
||||
/* Argument field reserved for the user; not touched by the driver. */
|
||||
intptr_t arg;
|
||||
|
||||
/* Pointer to the next descriptor. */
|
||||
struct i2c_descriptor* next;
|
||||
} i2c_descriptor;
|
||||
|
||||
/* Public API */
|
||||
|
||||
/* Aysnchronously enqueue a descriptor, optionally waiting on a timeout
|
||||
* if the queue is full. The exact behavior depends on 'q_mode':
|
||||
*
|
||||
* - I2C_Q_ADD
|
||||
* Always try to enqueue the descriptor.
|
||||
*
|
||||
* - I2C_Q_ONCE
|
||||
* Only attempt to enqueue the descriptor if no descriptor with the same
|
||||
* cookie is already running or queued. If this is not the case, then
|
||||
* returns I2C_RC_NOTADDED.
|
||||
*
|
||||
* - I2C_Q_REPLACE
|
||||
* If a descriptor with the same cookie is queued, replace it with this
|
||||
* descriptor and do not run the old descriptor's callbacks. If the
|
||||
* matching descriptor is running, returns I2C_RC_NOTADDED and does not
|
||||
* queue the new descriptor. If no match was found, then simply add the
|
||||
* new descriptor to the queue.
|
||||
*
|
||||
* The 'cookie' is only useful if you want to use the ONCE or REPLACE queue
|
||||
* modes, or if you want to use i2c_async_cancel(). Cookies used for queue
|
||||
* management must be reserved with i2c_async_reserve_cookies(), to prevent
|
||||
* different drivers from stepping on each other's toes.
|
||||
*
|
||||
* When you do not need queue management, you can use a 'cookie' of 0, which
|
||||
* is reserved for unmanaged transactions. Only use I2C_Q_ADD if you do this.
|
||||
*
|
||||
* Queuing is only successful if I2C_RC_OK is returned. All other codes
|
||||
* indicate that the descriptor was not queued, and therefore will not be
|
||||
* executed.
|
||||
*
|
||||
* Be careful about how/when you modify and queue descriptors. It's unsafe to
|
||||
* modify a queued descriptor: it could start running at any time, and the bus
|
||||
* might see a half-rewritten version of the descriptor. You _can_ queue the
|
||||
* same descriptor twice, since the i2c-async driver is not allowed to modify
|
||||
* any fields, but your callbacks need to be written with this case in mind.
|
||||
*
|
||||
* You can use queue management to help efficiently re-use descriptors.
|
||||
* Typically you can alternate between multiple descriptors, always keeping one
|
||||
* free to modify, and using your completion callbacks to cycle the free slot.
|
||||
* You can also probe with i2c_async_cancel() to ensure a specific descriptor
|
||||
* is not running before modifying it.
|
||||
*/
|
||||
extern int i2c_async_queue(int bus, int timeout, int q_mode,
|
||||
int cookie, i2c_descriptor* desc);
|
||||
|
||||
/* Cancel a queued descriptor. Searches the queue, starting with the running
|
||||
* descriptor, for a descriptor with a matching cookie, and attempts to remove
|
||||
* it from the queue.
|
||||
*
|
||||
* - Returns I2C_RC_NOTFOUND if no match was found.
|
||||
* - Returns I2C_RC_BUSY if the match is the currently running transaction.
|
||||
* - Returns I2C_RC_OK if the match was found in the pending queue and was
|
||||
* successfully removed from the queue.
|
||||
*/
|
||||
extern int i2c_async_cancel(int bus, int cookie);
|
||||
|
||||
/* Reserve a range of cookie values for use by a driver. This should only be
|
||||
* done once at startup. The driver doesn't care what cookies are used, so you
|
||||
* can manage them any way you like.
|
||||
*
|
||||
* A range [r, r+count) will be allocated, disjoint from all other allocated
|
||||
* ranges and with r >= 1. Returns 'r'.
|
||||
*/
|
||||
extern int i2c_async_reserve_cookies(int bus, int count);
|
||||
|
||||
/* Synchronous API to read, write, and modify registers. The register address
|
||||
* width is limited to 8 bits, although you can read and write multiple bytes.
|
||||
*
|
||||
* The modify operation can do a clear-and-set on a register with 8-bit values.
|
||||
* It also returns the original value of the register before modification, if
|
||||
* val != NULL.
|
||||
*/
|
||||
extern int i2c_reg_write(int bus, uint8_t addr, uint8_t reg,
|
||||
int count, const uint8_t* buf);
|
||||
extern int i2c_reg_read(int bus, uint8_t addr, uint8_t reg,
|
||||
int count, uint8_t* buf);
|
||||
extern int i2c_reg_modify1(int bus, uint8_t addr, uint8_t reg,
|
||||
uint8_t clr, uint8_t set, uint8_t* val);
|
||||
|
||||
/* Variant to write a single 8-bit value to a register */
|
||||
inline int i2c_reg_write1(int bus, uint8_t addr, uint8_t reg, uint8_t val)
|
||||
{
|
||||
return i2c_reg_write(bus, addr, reg, 1, &val);
|
||||
}
|
||||
|
||||
/* Variant to read an 8-bit value from a register; returns the value
|
||||
* directly, or returns -1 on any error. */
|
||||
inline int i2c_reg_read1(int bus, uint8_t addr, uint8_t reg)
|
||||
{
|
||||
uint8_t v;
|
||||
int i = i2c_reg_read(bus, addr, reg, 1, &v);
|
||||
if(i == I2C_STATUS_OK)
|
||||
return v;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Variant to set or clear one bit in an 8-bit register */
|
||||
inline int i2c_reg_setbit1(int bus, uint8_t addr, uint8_t reg,
|
||||
int bit, int value, uint8_t* val)
|
||||
{
|
||||
uint8_t clr = 0, set = 0;
|
||||
if(value)
|
||||
set = 1 << bit;
|
||||
else
|
||||
clr = 1 << bit;
|
||||
|
||||
return i2c_reg_modify1(bus, addr, reg, clr, set, val);
|
||||
}
|
||||
|
||||
/* Internal API */
|
||||
|
||||
/* Must be called by the target's i2c_init() before anyone uses I2C. */
|
||||
extern void __i2c_async_init(void);
|
||||
|
||||
/* Called by the target's interrupt handlers to signal completion of the
|
||||
* currently running descriptor. You must ensure IRQs are disabled before
|
||||
* calling this function.
|
||||
*
|
||||
* If another descriptor is queued for submission, either as part of the
|
||||
* same transaction or another one, then this may call __i2c_async_submit()
|
||||
* to start the next descriptor.
|
||||
*/
|
||||
extern void __i2c_async_complete_callback(int bus, int status);
|
||||
|
||||
/* Called by the i2c-async core to submit a descriptor to the hardware bus.
|
||||
* This function is implemented by the target. Just start the transfer and
|
||||
* unmask needed interrupts here, and try to return as quickly as possible.
|
||||
*/
|
||||
extern void __i2c_async_submit(int bus, i2c_descriptor* desc);
|
||||
|
||||
#endif /* HAVE_I2C_ASYNC */
|
||||
#endif /* __I2C_ASYNC_H__ */
|
Loading…
Reference in a new issue