/*************************************************************************** * __________ __ ___. * 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 #include #include /* 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__ */