mirror of
https://gitlab.rtems.org/rtems/rtos/rtems.git
synced 2026-09-22 14:07:25 +08:00
More i2c drivers from cpukit to bsps/shared to allow them use BSP infrastructure if needed. The framework code i2c-bus.c and i2c-dev.c kept at its original location. Signed-off-by: Michal Lenc <michallenc@seznam.cz>
1029 lines
27 KiB
C
1029 lines
27 KiB
C
/* SPDX-License-Identifier: BSD-2-Clause */
|
|
|
|
/*
|
|
* Copyright (c) 2016-2017 Chris Johns <chrisj@rtems.org>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* Xilinx AXI IIC Interface v2.0. See PG090.pdf.
|
|
*
|
|
* Note, only master support is provided and no dynamic mode by design.
|
|
*/
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <rtems.h>
|
|
#include <rtems/bspIo.h>
|
|
#include <rtems/irq-extension.h>
|
|
#include <rtems/score/assert.h>
|
|
|
|
#include <dev/i2c/i2c.h>
|
|
#include <dev/i2c/xilinx-axi-i2c.h>
|
|
|
|
/*
|
|
* Register map.
|
|
*/
|
|
#define REG_GIE 0x01c
|
|
#define REG_ISR 0x020
|
|
#define REG_IER 0x028
|
|
#define REG_SOFTR 0x040
|
|
#define REG_CR 0x100
|
|
#define REG_SR 0x104
|
|
#define REG_TX_FIFO 0x108
|
|
#define REG_RX_FIFO 0x10c
|
|
#define REG_ADR 0x110
|
|
#define REG_TX_FIFO_OCY 0x114
|
|
#define REG_RX_FIFO_OCY 0x118
|
|
#define REG_TEN_ADR 0x11c
|
|
#define REG_RX_FIFO_PIRQ 0x120
|
|
#define REG_GPO 0x124
|
|
#define REG_TSUSTA 0x128
|
|
#define REG_TSUSTO 0x12c
|
|
#define REG_THDSTA 0x130
|
|
#define REG_TSUDAT 0x134
|
|
#define REG_TBUF 0x138
|
|
#define REG_THIGH 0x13c
|
|
#define REG_TLOW 0x140
|
|
#define REG_THDDAT 0x144
|
|
|
|
/*
|
|
* Interrupts.
|
|
*/
|
|
#define INT_ARB_LOST ( 1 << 0 )
|
|
#define INT_TX_ERROR ( 1 << 1 )
|
|
#define INT_TX_FIFO_EMPTY ( 1 << 2 )
|
|
#define INT_RX_FIFO_FULL ( 1 << 3 )
|
|
#define INT_BUS_NOT_BUSY ( 1 << 4 )
|
|
#define INT_ADDRESSED_AS_SLAVE ( 1 << 5 )
|
|
#define INT_NOT_ADDRESSED_AS_SLAVE ( 1 << 6 )
|
|
#define INT_TX_FIFO_HALF_FULL ( 1 << 7 )
|
|
#define INT_ALL ( 0xff )
|
|
|
|
/*
|
|
* Command register.
|
|
*/
|
|
#define CR_EN ( 1 << 0 )
|
|
#define CR_TX_FIFO_RESET ( 1 << 1 )
|
|
#define CR_MSMS ( 1 << 2 )
|
|
#define CR_TX ( 1 << 3 )
|
|
#define CR_TXAK ( 1 << 4 )
|
|
#define CR_RSTA ( 1 << 5 )
|
|
#define CR_GC_EN ( 1 << 6 )
|
|
|
|
/*
|
|
* Status register.
|
|
*/
|
|
#define SR_ABGC ( 1 << 0 )
|
|
#define SR_AAS ( 1 << 1 )
|
|
#define SR_BB ( 1 << 2 )
|
|
#define SR_SRW ( 1 << 3 )
|
|
#define SR_TX_FIFO_FULL ( 1 << 4 )
|
|
#define SR_RX_FIFO_FULL ( 1 << 5 )
|
|
#define SR_RX_FIFO_EMPTY ( 1 << 6 )
|
|
#define SR_TX_FIFO_EMPTY ( 1 << 7 )
|
|
|
|
/*
|
|
* FIFO Sizes.
|
|
*/
|
|
#define TX_FIFO_SIZE 16
|
|
#define TX_FIFO_HALF_SIZE ( TX_FIFO_SIZE / 2 )
|
|
#define RX_FIFO_SIZE 16
|
|
|
|
/*
|
|
* Address flags.
|
|
*/
|
|
#define ADDR_TEN ( 1 << 31 )
|
|
#define ADDR_GPO ( 1 << 30 )
|
|
|
|
typedef struct {
|
|
i2c_bus base;
|
|
uintptr_t regs;
|
|
i2c_msg *msgs;
|
|
uint32_t msgs_remaining;
|
|
i2c_msg *current_msg;
|
|
uint32_t current_msg_todo;
|
|
uint8_t *current_msg_byte;
|
|
uint32_t current_todo;
|
|
uint32_t irqstatus;
|
|
bool read;
|
|
uint32_t addr;
|
|
rtems_id task_id;
|
|
bool gpo_address;
|
|
xilinx_aix_i2c_timing timing;
|
|
rtems_vector_number irq;
|
|
} xilinx_axi_i2c_bus;
|
|
|
|
xilinx_axi_i2c_bus *axi_i2c_bus;
|
|
|
|
/*
|
|
* Real-time trace buffering with a small overhead. The data can be dumped from
|
|
* gdb with:
|
|
*
|
|
* define axi-i2c
|
|
* set $i = 0
|
|
* while $i < axi_trace_in
|
|
* printf "%4d %08x %08x %08x : ", \
|
|
* $i, axi_trace[$i].vars[0], axi_trace[$i].vars[1], axi_trace[$i].vars[2]
|
|
* output axi_trace[$i].state
|
|
* printf "\n"
|
|
* set $i = $i + 1
|
|
* end
|
|
* end
|
|
*/
|
|
|
|
typedef enum {
|
|
AXI_I2C_NOP,
|
|
AXI_I2C_BEGIN,
|
|
AXI_I2C_END,
|
|
AXI_I2C_TRANSFER,
|
|
AXI_I2C_ADDRESS,
|
|
AXI_I2C_START_TRANSFER,
|
|
AXI_I2C_WRITE,
|
|
AXI_I2C_READ,
|
|
AXI_I2C_TX_FIFO,
|
|
AXI_I2C_RX_FIFO,
|
|
AXI_I2C_RX_FIFO_LEVEL,
|
|
AXI_I2C_INT,
|
|
AXI_I2C_INT_DONE,
|
|
AXI_I2C_INT_ERROR,
|
|
AXI_I2C_BUS_NOT_BUSY,
|
|
AXI_I2C_REG_WRITE,
|
|
AXI_I2C_REG_READ,
|
|
AXI_I2C_TIMEOUT,
|
|
AXI_I2C_WAKE
|
|
} axi_i2c_state;
|
|
|
|
#define RTEMS_AXI_I2C_TRACE 0
|
|
#if RTEMS_AXI_I2C_TRACE
|
|
|
|
#define DRIVER_REG_TRACE 1
|
|
|
|
typedef struct {
|
|
axi_i2c_state state;
|
|
uint32_t vars[ 3 ];
|
|
} axi_i2c_trace;
|
|
|
|
#define AXI_I2C_TRACE 5000
|
|
axi_i2c_trace axi_trace[ AXI_I2C_TRACE ];
|
|
int axi_trace_in;
|
|
|
|
static inline void axi_trace_reset( void )
|
|
{
|
|
axi_trace_in = 0;
|
|
}
|
|
|
|
static inline void axi_trace_append(
|
|
axi_i2c_state state,
|
|
uint32_t v1,
|
|
uint32_t v2,
|
|
uint32_t v3
|
|
)
|
|
{
|
|
if ( axi_trace_in < AXI_I2C_TRACE ) {
|
|
axi_i2c_trace rec = { state, { v1, v2, v3 } };
|
|
axi_trace[ axi_trace_in++ ] = rec;
|
|
}
|
|
}
|
|
#else
|
|
#define axi_trace_reset()
|
|
#define axi_trace_append( s, v1, v2, v3 )
|
|
#endif
|
|
|
|
#define DRIVER_DEBUG 0
|
|
#define DRIVER_DEBUG_DEFAULT true
|
|
#if DRIVER_DEBUG
|
|
#ifndef RTEMS_PRINTFLIKE
|
|
#define RTEMS_PRINTFLIKE( _format_pos, _ap_pos ) \
|
|
__attribute__(( __format__( __printf__, _format_pos, _ap_pos ) ))
|
|
#endif
|
|
static bool drv_printk_enable = DRIVER_DEBUG_DEFAULT;
|
|
static void drv_printk( const char *format, ... ) RTEMS_PRINTFLIKE( 1, 2 );
|
|
static void drv_printk( const char *format, ... )
|
|
{
|
|
va_list ap;
|
|
va_start( ap, format );
|
|
if ( drv_printk_enable ) {
|
|
vprintk( format, ap );
|
|
}
|
|
va_end( ap );
|
|
}
|
|
#else
|
|
#define drv_printk( _fmt, ... )
|
|
#endif
|
|
|
|
static inline void xilinx_axi_i2c_reg_write(
|
|
const xilinx_axi_i2c_bus *bus,
|
|
uint32_t reg,
|
|
uint32_t value
|
|
)
|
|
{
|
|
#if DRIVER_REG_TRACE
|
|
axi_trace_append( AXI_I2C_REG_WRITE, reg, value, 0 );
|
|
#endif
|
|
*( (volatile uint32_t *) ( bus->regs + reg ) ) = value;
|
|
}
|
|
|
|
static inline uint32_t xilinx_axi_i2c_reg_read(
|
|
const xilinx_axi_i2c_bus *bus,
|
|
uint32_t reg
|
|
)
|
|
{
|
|
uint32_t value = *( (volatile uint32_t *) ( bus->regs + reg ) );
|
|
#if DRIVER_REG_TRACE
|
|
axi_trace_append( AXI_I2C_REG_READ, reg, value, 0 );
|
|
#endif
|
|
return value;
|
|
}
|
|
|
|
static uint32_t xilinx_axi_i2c_read_irq_status( const xilinx_axi_i2c_bus *bus )
|
|
{
|
|
return xilinx_axi_i2c_reg_read( bus, REG_ISR );
|
|
}
|
|
|
|
static uint32_t xilinx_axi_i2c_read_irq_enabled(
|
|
const xilinx_axi_i2c_bus *bus
|
|
)
|
|
{
|
|
return xilinx_axi_i2c_reg_read( bus, REG_IER );
|
|
}
|
|
|
|
static void xilinx_axi_i2c_clear_irq(
|
|
const xilinx_axi_i2c_bus *bus,
|
|
uint32_t mask
|
|
)
|
|
{
|
|
/*
|
|
* The ISR bits can be toggled so only write a 1 if set.
|
|
*/
|
|
xilinx_axi_i2c_reg_write(
|
|
bus,
|
|
REG_ISR,
|
|
xilinx_axi_i2c_reg_read( bus, REG_ISR ) & mask
|
|
);
|
|
}
|
|
|
|
static inline void xilinx_axi_i2c_enable_irq(
|
|
const xilinx_axi_i2c_bus *bus,
|
|
uint32_t mask
|
|
)
|
|
{
|
|
xilinx_axi_i2c_reg_write(
|
|
bus,
|
|
REG_IER,
|
|
xilinx_axi_i2c_reg_read( bus, REG_IER ) | mask
|
|
);
|
|
}
|
|
|
|
static inline void xilinx_axi_i2c_disable_irq(
|
|
const xilinx_axi_i2c_bus *bus,
|
|
uint32_t mask
|
|
)
|
|
{
|
|
xilinx_axi_i2c_reg_write(
|
|
bus,
|
|
REG_IER,
|
|
xilinx_axi_i2c_reg_read( bus, REG_IER ) & ~mask
|
|
);
|
|
}
|
|
|
|
static void xilinx_axi_i2c_clear_enable_irq(
|
|
const xilinx_axi_i2c_bus *bus,
|
|
uint32_t mask
|
|
)
|
|
{
|
|
xilinx_axi_i2c_clear_irq( bus, mask );
|
|
xilinx_axi_i2c_enable_irq( bus, mask );
|
|
}
|
|
|
|
static void xilinx_axi_i2c_disable_clear_irq(
|
|
const xilinx_axi_i2c_bus *bus,
|
|
uint32_t mask
|
|
)
|
|
{
|
|
xilinx_axi_i2c_disable_irq( bus, mask );
|
|
xilinx_axi_i2c_clear_irq( bus, mask );
|
|
}
|
|
|
|
static inline void xilinx_axi_i2c_disable_all_irq(
|
|
const xilinx_axi_i2c_bus *bus
|
|
)
|
|
{
|
|
xilinx_axi_i2c_reg_write( bus, REG_GIE, 0 );
|
|
}
|
|
|
|
static void xilinx_axi_i2c_enable_interrupts( const xilinx_axi_i2c_bus *bus )
|
|
{
|
|
xilinx_axi_i2c_reg_write( bus, REG_GIE, 1 << 31 );
|
|
}
|
|
|
|
static void xilinx_axi_i2c_disable_interrupts( const xilinx_axi_i2c_bus *bus )
|
|
{
|
|
xilinx_axi_i2c_reg_write( bus, REG_GIE, 0 );
|
|
xilinx_axi_i2c_reg_write( bus, REG_IER, 0 );
|
|
}
|
|
|
|
static inline void xilinx_axi_i2c_write_cr(
|
|
const xilinx_axi_i2c_bus *bus,
|
|
uint32_t value
|
|
)
|
|
{
|
|
xilinx_axi_i2c_reg_write( bus, REG_CR, value );
|
|
}
|
|
|
|
static inline uint32_t xilinx_axi_i2c_read_cr( const xilinx_axi_i2c_bus *bus )
|
|
{
|
|
return xilinx_axi_i2c_reg_read( bus, REG_CR );
|
|
}
|
|
|
|
static inline void xilinx_axi_i2c_set_cr(
|
|
const xilinx_axi_i2c_bus *bus,
|
|
uint32_t mask
|
|
)
|
|
{
|
|
xilinx_axi_i2c_reg_write(
|
|
bus,
|
|
REG_CR,
|
|
xilinx_axi_i2c_reg_read( bus, REG_CR ) | mask
|
|
);
|
|
}
|
|
|
|
static inline void xilinx_axi_i2c_clear_cr(
|
|
const xilinx_axi_i2c_bus *bus,
|
|
uint32_t mask
|
|
)
|
|
{
|
|
xilinx_axi_i2c_write_cr( bus, xilinx_axi_i2c_read_cr( bus ) & ~mask );
|
|
}
|
|
|
|
static inline uint32_t xilinx_axi_i2c_read_sr( const xilinx_axi_i2c_bus *bus )
|
|
{
|
|
return xilinx_axi_i2c_reg_read( bus, REG_SR );
|
|
}
|
|
|
|
static inline uint32_t xilinx_axi_i2c_read_rx_level(
|
|
const xilinx_axi_i2c_bus *bus
|
|
)
|
|
{
|
|
if ( ( xilinx_axi_i2c_read_sr( bus ) & SR_RX_FIFO_EMPTY ) != 0 ) {
|
|
return 0;
|
|
}
|
|
return xilinx_axi_i2c_reg_read( bus, REG_RX_FIFO_OCY ) + 1;
|
|
}
|
|
|
|
static inline void xilinx_axi_i2c_write_rx_pirq(
|
|
const xilinx_axi_i2c_bus *bus,
|
|
uint32_t level
|
|
)
|
|
{
|
|
if ( level != 0 ) {
|
|
xilinx_axi_i2c_reg_write( bus, REG_RX_FIFO_PIRQ, level - 1 );
|
|
} else {
|
|
xilinx_axi_i2c_reg_write( bus, REG_RX_FIFO_PIRQ, level );
|
|
}
|
|
}
|
|
|
|
static inline uint32_t xilinx_axi_i2c_read_tx_space(
|
|
const xilinx_axi_i2c_bus *bus
|
|
)
|
|
{
|
|
if ( ( xilinx_axi_i2c_read_sr( bus ) & SR_TX_FIFO_EMPTY ) != 0 ) {
|
|
return TX_FIFO_SIZE;
|
|
}
|
|
return TX_FIFO_SIZE - xilinx_axi_i2c_reg_read( bus, REG_TX_FIFO_OCY ) - 1;
|
|
}
|
|
|
|
static void xilinx_axi_i2c_write_tx_fifo_data(
|
|
xilinx_axi_i2c_bus *bus,
|
|
uint32_t data
|
|
)
|
|
{
|
|
axi_trace_append( AXI_I2C_TX_FIFO, data, 0, 0 );
|
|
xilinx_axi_i2c_reg_write( bus, REG_TX_FIFO, data );
|
|
}
|
|
|
|
static inline uint32_t xilinx_axi_i2c_read_rx_fifo_data(
|
|
xilinx_axi_i2c_bus *bus
|
|
)
|
|
{
|
|
uint32_t data = xilinx_axi_i2c_reg_read( bus, REG_RX_FIFO );
|
|
axi_trace_append( AXI_I2C_RX_FIFO, data, 0, 0 );
|
|
return data;
|
|
}
|
|
|
|
static void xilinx_axi_i2c_reset( xilinx_axi_i2c_bus *bus )
|
|
{
|
|
xilinx_axi_i2c_reg_write( bus, REG_SOFTR, 0x0a );
|
|
if ( ( bus->timing.valid_mask & XILINX_AIX_I2C_ALL_REGS ) != 0 ) {
|
|
static const uint32_t r[ 8 ] = {
|
|
REG_TSUSTA,
|
|
REG_TSUSTO,
|
|
REG_THDSTA,
|
|
REG_TSUDAT,
|
|
REG_TBUF,
|
|
REG_THIGH,
|
|
REG_TLOW,
|
|
REG_THDDAT
|
|
};
|
|
static const uint32_t m[ 8 ] = {
|
|
XILINX_AIX_I2C_TSUSTA,
|
|
XILINX_AIX_I2C_TSUSTO,
|
|
XILINX_AIX_I2C_THDSTA,
|
|
XILINX_AIX_I2C_TSUDAT,
|
|
XILINX_AIX_I2C_TBUF,
|
|
XILINX_AIX_I2C_THIGH,
|
|
XILINX_AIX_I2C_TLOW,
|
|
XILINX_AIX_I2C_THDDAT
|
|
};
|
|
uint32_t vm = bus->timing.valid_mask;
|
|
const uint32_t *u = &bus->timing.TSUSTA;
|
|
size_t i;
|
|
for ( i = 0; i < ( sizeof( r ) / sizeof( r[ 0 ] ) ); ++i, ++u ) {
|
|
if ( ( vm & m[ i ] ) != 0 ) {
|
|
xilinx_axi_i2c_reg_write( bus, r[ i ], *u );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void xilinx_axi_i2c_reinit( xilinx_axi_i2c_bus *bus )
|
|
{
|
|
drv_printk( "axi-i2c: reinit\n" );
|
|
xilinx_axi_i2c_reset( bus );
|
|
xilinx_axi_i2c_write_rx_pirq( bus, RX_FIFO_SIZE );
|
|
xilinx_axi_i2c_write_cr( bus, CR_TX_FIFO_RESET );
|
|
xilinx_axi_i2c_write_cr( bus, CR_EN );
|
|
xilinx_axi_i2c_clear_enable_irq( bus, INT_ARB_LOST );
|
|
}
|
|
|
|
static void xilinx_axi_i2c_wakeup( xilinx_axi_i2c_bus *bus )
|
|
{
|
|
axi_trace_append( AXI_I2C_WAKE, bus->task_id, bus->irqstatus, 0 );
|
|
drv_printk( "axi-i2c: wakeup: irqstatus: %08lx\n", bus->irqstatus );
|
|
rtems_status_code sc = rtems_event_transient_send( bus->task_id );
|
|
_Assert( sc == RTEMS_SUCCESSFUL );
|
|
(void) sc;
|
|
}
|
|
|
|
static void xilinx_axi_i2c_next_byte( xilinx_axi_i2c_bus *bus )
|
|
{
|
|
--bus->current_todo;
|
|
--bus->current_msg_todo;
|
|
++bus->current_msg_byte;
|
|
if ( bus->current_msg_todo == 0 ) {
|
|
if (
|
|
bus->msgs_remaining != 0 && ( bus->msgs[ 0 ].flags & I2C_M_NOSTART ) != 0
|
|
) {
|
|
bus->current_msg_todo = bus->msgs[ 0 ].len;
|
|
bus->current_msg_byte = bus->msgs[ 0 ].buf;
|
|
++bus->msgs;
|
|
--bus->msgs_remaining;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void xilinx_axi_i2c_read_rx_byte( xilinx_axi_i2c_bus *bus )
|
|
{
|
|
*bus->current_msg_byte = (uint8_t) xilinx_axi_i2c_read_rx_fifo_data( bus );
|
|
xilinx_axi_i2c_next_byte( bus );
|
|
}
|
|
|
|
static void xilinx_axi_i2c_read_rx_bytes(
|
|
xilinx_axi_i2c_bus *bus,
|
|
uint32_t count
|
|
)
|
|
{
|
|
while ( count-- > 0 ) {
|
|
xilinx_axi_i2c_read_rx_byte( bus );
|
|
}
|
|
}
|
|
|
|
static void xilinx_axi_i2c_set_rx_fifo_level( xilinx_axi_i2c_bus *bus )
|
|
{
|
|
uint32_t size;
|
|
if ( bus->current_todo > RX_FIFO_SIZE ) {
|
|
size = RX_FIFO_SIZE;
|
|
} else {
|
|
size = bus->current_todo - 1;
|
|
}
|
|
axi_trace_append( AXI_I2C_RX_FIFO_LEVEL, size, 0, 0 );
|
|
xilinx_axi_i2c_write_rx_pirq( bus, size );
|
|
}
|
|
|
|
static bool xilinx_axi_i2c_start_transfer( xilinx_axi_i2c_bus *bus );
|
|
|
|
static bool xilinx_axi_i2c_read_rx_fifo( xilinx_axi_i2c_bus *bus )
|
|
{
|
|
drv_printk( "axi-i2c: read rx fifo: length:%lu\n", bus->current_todo );
|
|
|
|
if ( bus->current_todo == 0 ) {
|
|
return false;
|
|
}
|
|
|
|
if ( ( xilinx_axi_i2c_read_sr( bus ) & SR_RX_FIFO_EMPTY ) == 0 ) {
|
|
uint32_t level = xilinx_axi_i2c_read_rx_level( bus );
|
|
bool active;
|
|
|
|
drv_printk( "axi-i2c: read rx fifo: level:%lu\n", level );
|
|
|
|
if ( level > bus->current_todo ) {
|
|
level = bus->current_todo;
|
|
}
|
|
|
|
switch ( bus->current_todo - level ) {
|
|
case 1:
|
|
drv_printk( "axi-i2c: read rx fifo: one more\n" );
|
|
/*
|
|
* One more byte to be received. This is set up by programming the RX
|
|
* FIFO programmable depth interrupt register with a value that is 2
|
|
* less than the number we need (the register is minus 1). When we have
|
|
* one byte left, disable the TX error interrupt because setting the NO
|
|
* ACK bit in the command register causes a TX error interrupt. Set the
|
|
* TXAK bit in the CR to not-acknowledge the next byte received telling
|
|
* the slave sender the master accepts no more data, then read the
|
|
* FIFO. If the FIFO is ready before the TXAK bit is set the slave will
|
|
* see a request for more data. We will come back to the next case
|
|
* statement for the last byte once it has been received.
|
|
*/
|
|
xilinx_axi_i2c_disable_clear_irq( bus, INT_TX_ERROR );
|
|
xilinx_axi_i2c_set_cr( bus, CR_TXAK );
|
|
xilinx_axi_i2c_read_rx_bytes( bus, level );
|
|
/*
|
|
* Set the RX PIRQ to 0 after the RX data has been read. There is an
|
|
* observed timing issue and glitch if written before.
|
|
*/
|
|
xilinx_axi_i2c_write_rx_pirq( bus, 0 );
|
|
break;
|
|
|
|
case 0:
|
|
drv_printk( "axi-i2c: read rx fifo: no more\n" );
|
|
/*
|
|
* We should have 1 byte in the FIFO which is the last byte received
|
|
* with a NACK. If there are no more message we need to send a STOP by
|
|
* clearing he MSMS bit in the CR and then waiting for the bus to not
|
|
* be busy.
|
|
*/
|
|
xilinx_axi_i2c_disable_clear_irq(
|
|
bus,
|
|
INT_RX_FIFO_FULL | INT_TX_ERROR
|
|
);
|
|
if ( bus->msgs_remaining == 0 ) {
|
|
xilinx_axi_i2c_clear_cr( bus, CR_MSMS );
|
|
xilinx_axi_i2c_clear_enable_irq( bus, INT_BUS_NOT_BUSY );
|
|
active = true;
|
|
}
|
|
xilinx_axi_i2c_read_rx_byte( bus );
|
|
if ( bus->msgs_remaining != 0 ) {
|
|
active = xilinx_axi_i2c_start_transfer( bus );
|
|
}
|
|
return active;
|
|
|
|
default:
|
|
drv_printk(
|
|
"axi-i2c: read rx fifo: more:%lu\n",
|
|
bus->current_todo - level
|
|
);
|
|
/*
|
|
* All the requested data is in the FIFO so read it and update the PIRQ
|
|
* level. The PIRQ size is always one less than the maximum size.
|
|
*/
|
|
xilinx_axi_i2c_read_rx_bytes( bus, level );
|
|
if ( bus->current_todo > RX_FIFO_SIZE ) {
|
|
xilinx_axi_i2c_write_rx_pirq( bus, RX_FIFO_SIZE );
|
|
} else {
|
|
xilinx_axi_i2c_write_rx_pirq( bus, bus->current_todo - 1 );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void xilinx_axi_i2c_write_tx_byte( xilinx_axi_i2c_bus *bus )
|
|
{
|
|
xilinx_axi_i2c_write_tx_fifo_data( bus, *bus->current_msg_byte );
|
|
xilinx_axi_i2c_next_byte( bus );
|
|
}
|
|
|
|
static void xilinx_axi_i2c_write_tx_bytes( xilinx_axi_i2c_bus *bus )
|
|
{
|
|
uint32_t space = xilinx_axi_i2c_read_tx_space( bus );
|
|
uint32_t level = bus->current_todo - 1;
|
|
uint32_t i;
|
|
drv_printk( "axi-i2c: tx fifo load: space:%lu level:%lu\n", space, level );
|
|
if ( level < space ) {
|
|
space = level;
|
|
}
|
|
for ( i = 0; i < space; ++i ) {
|
|
xilinx_axi_i2c_write_tx_byte( bus );
|
|
}
|
|
}
|
|
|
|
static bool xilinx_axi_i2c_write_tx_fifo( xilinx_axi_i2c_bus *bus )
|
|
{
|
|
bool more = true;
|
|
drv_printk(
|
|
"axi-i2c: write tx fifo: current_todo: %lu\n",
|
|
bus->current_todo
|
|
);
|
|
switch ( bus->current_todo ) {
|
|
case 0:
|
|
xilinx_axi_i2c_disable_clear_irq(
|
|
bus,
|
|
INT_TX_FIFO_EMPTY | INT_TX_FIFO_HALF_FULL | INT_TX_ERROR |
|
|
INT_BUS_NOT_BUSY
|
|
);
|
|
more = xilinx_axi_i2c_start_transfer( bus );
|
|
break;
|
|
case 1:
|
|
/*
|
|
* If transmitting and the last byte issue a stop and wait for the bus to
|
|
* not be busy.
|
|
*/
|
|
if ( !bus->read && bus->msgs_remaining == 0 ) {
|
|
xilinx_axi_i2c_clear_cr( bus, CR_MSMS );
|
|
xilinx_axi_i2c_clear_enable_irq( bus, INT_BUS_NOT_BUSY );
|
|
}
|
|
xilinx_axi_i2c_write_tx_byte( bus );
|
|
break;
|
|
default:
|
|
xilinx_axi_i2c_write_tx_bytes( bus );
|
|
break;
|
|
}
|
|
return more;
|
|
}
|
|
|
|
static void xilinx_axi_i2c_write_address( xilinx_axi_i2c_bus *bus )
|
|
{
|
|
if ( ( bus->addr & ADDR_GPO ) != 0 ) {
|
|
xilinx_axi_i2c_reg_write( bus, REG_GPO, ( bus->addr >> 12 ) & 0xf );
|
|
}
|
|
if ( ( bus->addr & ADDR_TEN ) != 0 ) {
|
|
xilinx_axi_i2c_write_tx_fifo_data( bus, ( bus->addr >> 8 ) & 0xff );
|
|
}
|
|
xilinx_axi_i2c_write_tx_fifo_data( bus, bus->addr & 0xff );
|
|
}
|
|
|
|
static void xilinx_axi_i2c_start_read( xilinx_axi_i2c_bus *bus )
|
|
{
|
|
uint32_t cr;
|
|
uint32_t set = INT_RX_FIFO_FULL;
|
|
axi_trace_append( AXI_I2C_READ, bus->current_todo, 0, 0 );
|
|
drv_printk( "axi-i2c: start read: size: %lu\n", bus->current_todo );
|
|
/*
|
|
* Is this a restart? If there is no active STOP it is a restart.
|
|
*/
|
|
cr = xilinx_axi_i2c_read_cr( bus );
|
|
if ( ( cr & CR_MSMS ) != 0 ) {
|
|
cr |= CR_RSTA;
|
|
xilinx_axi_i2c_write_cr( bus, cr );
|
|
}
|
|
xilinx_axi_i2c_write_address( bus );
|
|
xilinx_axi_i2c_set_rx_fifo_level( bus );
|
|
/*
|
|
* We must NACK the last byte so if we are receiving a single byte issue a
|
|
* NACK.
|
|
*/
|
|
cr &= ~( CR_TX | CR_TXAK );
|
|
if ( bus->current_todo == 1 ) {
|
|
cr |= CR_TXAK;
|
|
} else {
|
|
set |= INT_TX_ERROR;
|
|
}
|
|
/*
|
|
* Issue a start.
|
|
*/
|
|
cr |= CR_MSMS;
|
|
xilinx_axi_i2c_clear_enable_irq( bus, set );
|
|
xilinx_axi_i2c_write_cr( bus, cr );
|
|
}
|
|
|
|
static void xilinx_axi_i2c_start_write( xilinx_axi_i2c_bus *bus )
|
|
{
|
|
uint32_t space;
|
|
uint32_t enable;
|
|
uint32_t cr;
|
|
axi_trace_append( AXI_I2C_WRITE, bus->current_todo, 0, 0 );
|
|
cr = xilinx_axi_i2c_read_cr( bus );
|
|
/*
|
|
* If a master issue a restart if there is no active STOP on the bus.
|
|
*/
|
|
if ( ( cr & CR_MSMS ) != 0 ) {
|
|
cr |= CR_RSTA;
|
|
xilinx_axi_i2c_write_cr( bus, cr );
|
|
}
|
|
xilinx_axi_i2c_write_address( bus );
|
|
if ( bus->current_todo > 1 ) {
|
|
xilinx_axi_i2c_write_tx_bytes( bus );
|
|
}
|
|
space = xilinx_axi_i2c_read_tx_space( bus );
|
|
enable = INT_TX_FIFO_EMPTY | INT_TX_ERROR;
|
|
if ( space > TX_FIFO_HALF_SIZE && bus->current_todo > 1 ) {
|
|
enable |= INT_TX_FIFO_HALF_FULL;
|
|
}
|
|
xilinx_axi_i2c_clear_enable_irq( bus, enable );
|
|
cr &= ~CR_TXAK;
|
|
cr |= CR_MSMS | CR_TX;
|
|
xilinx_axi_i2c_write_cr( bus, cr );
|
|
}
|
|
|
|
static bool xilinx_axi_i2c_start_transfer( xilinx_axi_i2c_bus *bus )
|
|
{
|
|
const i2c_msg *msgs = bus->msgs;
|
|
uint32_t msg_todo = bus->msgs_remaining;
|
|
uint32_t i;
|
|
|
|
axi_trace_append( AXI_I2C_START_TRANSFER, msg_todo, 0, 0 );
|
|
drv_printk( "axi-i2c: start transfer: messages: %lu\n", msg_todo );
|
|
|
|
if ( msg_todo == 0 ) {
|
|
xilinx_axi_i2c_clear_cr( bus, CR_MSMS );
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Get the amount of data to transfer. It can span message buffers if the
|
|
* I2C_M_NOSTART flag is set.
|
|
*/
|
|
bus->current_todo = msgs[ 0 ].len;
|
|
for (
|
|
i = 1; i < msg_todo && ( msgs[ i ].flags & I2C_M_NOSTART ) != 0; ++i
|
|
) {
|
|
bus->current_todo += msgs[ i ].len;
|
|
}
|
|
|
|
bus->read = ( msgs->flags & I2C_M_RD ) != 0;
|
|
|
|
if ( ( msgs->flags & I2C_M_TEN ) != 0 ) {
|
|
bus->addr =
|
|
( ADDR_TEN | ( ( msgs->addr & ( 3 << 8 ) ) << 1 ) |
|
|
( ( bus->read ? 1 : 0 ) << 8 ) | ( msgs->addr & 0xff ) );
|
|
} else {
|
|
bus->addr = ( msgs->addr & 0x7f ) << 1 | ( bus->read ? 1 : 0 );
|
|
}
|
|
|
|
if ( bus->gpo_address ) {
|
|
bus->addr |= ADDR_GPO | ( msgs->addr & 0xf000 );
|
|
}
|
|
|
|
axi_trace_append(
|
|
AXI_I2C_TRANSFER,
|
|
bus->msgs_remaining,
|
|
bus->current_todo,
|
|
bus->addr
|
|
);
|
|
|
|
/*
|
|
* The bus->msgs is left pointing to the next message because we may need to
|
|
* start a new message while completing the current message.
|
|
*/
|
|
bus->current_msg_todo = msgs[ 0 ].len;
|
|
bus->current_msg_byte = msgs[ 0 ].buf;
|
|
++bus->msgs;
|
|
--bus->msgs_remaining;
|
|
|
|
if ( bus->read ) {
|
|
xilinx_axi_i2c_start_read( bus );
|
|
} else {
|
|
xilinx_axi_i2c_start_write( bus );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void xilinx_axi_i2c_interrupt( void *arg )
|
|
{
|
|
xilinx_axi_i2c_bus *bus = arg;
|
|
uint32_t status = xilinx_axi_i2c_read_irq_status( bus );
|
|
uint32_t enabled = xilinx_axi_i2c_read_irq_enabled( bus );
|
|
uint32_t active = status & enabled;
|
|
uint32_t clear = 0;
|
|
int done = 0;
|
|
|
|
axi_trace_append( AXI_I2C_INT, active, status, enabled );
|
|
|
|
drv_printk(
|
|
"axi-i2c: interrupt: active:%02lx isr:%02lx ier:%02lx\n",
|
|
active,
|
|
status,
|
|
enabled
|
|
);
|
|
|
|
/*
|
|
* An error or we lost arbitration. If transmitting and there is more data to
|
|
* send a INT_TX_ERROR means the slave issue a NOT ACK because there was not
|
|
* slave at the address or the addressed slave will not accept any more data.
|
|
*
|
|
* Clean up and wake the user.
|
|
*/
|
|
if (
|
|
( ( active & INT_ARB_LOST ) != 0 ) ||
|
|
( !bus->read && ( active & INT_TX_ERROR ) != 0 )
|
|
) {
|
|
bus->irqstatus = active & ( INT_ARB_LOST | INT_TX_ERROR );
|
|
axi_trace_append( AXI_I2C_INT_ERROR, bus->irqstatus, 0, 0 );
|
|
xilinx_axi_i2c_reinit( bus );
|
|
xilinx_axi_i2c_clear_cr( bus, CR_EN );
|
|
xilinx_axi_i2c_wakeup( bus );
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* RX FIFO full?
|
|
*/
|
|
if ( ( active & INT_RX_FIFO_FULL ) != 0 ) {
|
|
clear |= INT_RX_FIFO_FULL;
|
|
|
|
if ( bus->read && !xilinx_axi_i2c_read_rx_fifo( bus ) ) {
|
|
++done;
|
|
axi_trace_append( AXI_I2C_INT_DONE, done, clear, 0 );
|
|
}
|
|
|
|
if ( bus->current_todo == 0 ) {
|
|
clear |= status & INT_TX_ERROR;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* TX FIFO empty or half empty?
|
|
*/
|
|
if ( ( active & ( INT_TX_FIFO_EMPTY | INT_TX_FIFO_HALF_FULL ) ) != 0 ) {
|
|
clear |= active & ( INT_TX_FIFO_EMPTY | INT_TX_FIFO_HALF_FULL );
|
|
|
|
if ( !bus->read && !xilinx_axi_i2c_write_tx_fifo( bus ) ) {
|
|
++done;
|
|
axi_trace_append( AXI_I2C_INT_DONE, done, clear, 1 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Gate the bus not busy interrupt with the bus busy status to know the bus
|
|
* is really not busy. It could be an interrupt left over from starting the
|
|
* transmission.
|
|
*/
|
|
if ( ( active & INT_BUS_NOT_BUSY ) != 0 ) {
|
|
if ( ( xilinx_axi_i2c_read_sr( bus ) & SR_BB ) == 0 ) {
|
|
xilinx_axi_i2c_disable_clear_irq( bus, INT_BUS_NOT_BUSY );
|
|
if ( bus->read && !xilinx_axi_i2c_read_rx_fifo( bus ) ) {
|
|
++done;
|
|
axi_trace_append( AXI_I2C_BUS_NOT_BUSY, done, clear, 0 );
|
|
} else if ( !bus->read && !xilinx_axi_i2c_write_tx_fifo( bus ) ) {
|
|
++done;
|
|
axi_trace_append( AXI_I2C_BUS_NOT_BUSY, done, clear, 1 );
|
|
}
|
|
} else {
|
|
clear |= INT_BUS_NOT_BUSY;
|
|
}
|
|
}
|
|
|
|
if ( clear != 0 ) {
|
|
xilinx_axi_i2c_clear_irq( bus, clear );
|
|
}
|
|
|
|
if ( done != 0 ) {
|
|
xilinx_axi_i2c_disable_interrupts( bus );
|
|
xilinx_axi_i2c_clear_cr( bus, CR_EN );
|
|
xilinx_axi_i2c_wakeup( bus );
|
|
}
|
|
}
|
|
|
|
static int xilinx_axi_i2c_transfer(
|
|
i2c_bus *base,
|
|
i2c_msg *msgs,
|
|
uint32_t msg_count
|
|
)
|
|
{
|
|
xilinx_axi_i2c_bus *bus = (xilinx_axi_i2c_bus *) base;
|
|
rtems_status_code sc;
|
|
int r = 0;
|
|
|
|
axi_trace_reset();
|
|
axi_trace_append( AXI_I2C_BEGIN, msg_count, 0, 0 );
|
|
|
|
drv_printk( "axi-i2c: i2c transfer\n" );
|
|
|
|
_Assert( msg_count > 0 );
|
|
|
|
bus->msgs = &msgs[ 0 ];
|
|
bus->msgs_remaining = msg_count;
|
|
bus->irqstatus = 0;
|
|
bus->task_id = rtems_task_self();
|
|
|
|
xilinx_axi_i2c_reinit( bus );
|
|
xilinx_axi_i2c_start_transfer( bus );
|
|
xilinx_axi_i2c_enable_interrupts( bus );
|
|
|
|
sc = rtems_event_transient_receive( RTEMS_WAIT, bus->base.timeout );
|
|
if ( sc != RTEMS_SUCCESSFUL ) {
|
|
axi_trace_append( AXI_I2C_TIMEOUT, 0, 0, 0 );
|
|
xilinx_axi_i2c_reinit( bus );
|
|
rtems_event_transient_clear();
|
|
r = -ETIMEDOUT;
|
|
}
|
|
|
|
if ( r == 0 && bus->irqstatus != 0 ) {
|
|
r = -EIO;
|
|
}
|
|
|
|
axi_trace_append( AXI_I2C_END, bus->irqstatus, r, 0 );
|
|
|
|
return r;
|
|
}
|
|
|
|
static int xilinx_axi_i2c_set_clock( i2c_bus *base, unsigned long clock )
|
|
{
|
|
xilinx_axi_i2c_bus *bus = (xilinx_axi_i2c_bus *) base;
|
|
|
|
if ( ( bus->timing.valid_mask & XILINX_AIX_I2C_AXI_CLOCK ) == 0 ) {
|
|
return -EIO;
|
|
}
|
|
|
|
bus->timing.THIGH = ( bus->timing.AXI_CLOCK / ( 2 * clock ) ) - 7 -
|
|
bus->timing.SCL_INERTIAL_DELAY;
|
|
bus->timing.TLOW = bus->timing.THIGH;
|
|
|
|
bus->timing.valid_mask |= XILINX_AIX_I2C_THIGH | XILINX_AIX_I2C_TLOW;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void xilinx_axi_i2c_destroy( i2c_bus *base )
|
|
{
|
|
xilinx_axi_i2c_bus *bus = (xilinx_axi_i2c_bus *) base;
|
|
rtems_status_code sc;
|
|
|
|
sc = rtems_interrupt_handler_remove(
|
|
bus->irq,
|
|
xilinx_axi_i2c_interrupt,
|
|
bus
|
|
);
|
|
_Assert( sc == RTEMS_SUCCESSFUL );
|
|
(void) sc;
|
|
|
|
i2c_bus_destroy_and_free( &bus->base );
|
|
}
|
|
|
|
int i2c_bus_register_xilinx_aix_i2c(
|
|
const char *bus_path,
|
|
uintptr_t register_base,
|
|
rtems_vector_number irq,
|
|
bool gpo_address,
|
|
const xilinx_aix_i2c_timing *timing
|
|
)
|
|
{
|
|
xilinx_axi_i2c_bus *bus;
|
|
rtems_status_code sc;
|
|
|
|
bus = (xilinx_axi_i2c_bus *) i2c_bus_alloc_and_init( sizeof( *bus ) );
|
|
if ( bus == NULL ) {
|
|
return -1;
|
|
}
|
|
|
|
bus->regs = register_base;
|
|
bus->irq = irq;
|
|
bus->gpo_address = gpo_address;
|
|
bus->timing = *timing;
|
|
|
|
sc = rtems_interrupt_handler_install(
|
|
irq,
|
|
"Xilinx AXI I2C",
|
|
RTEMS_INTERRUPT_UNIQUE,
|
|
xilinx_axi_i2c_interrupt,
|
|
bus
|
|
);
|
|
if ( sc != RTEMS_SUCCESSFUL ) {
|
|
drv_printk( "axi-i2c: interrupt attach failed\n" );
|
|
( *bus->base.destroy )( &bus->base );
|
|
rtems_set_errno_and_return_minus_one( EIO );
|
|
}
|
|
|
|
bus->base.transfer = xilinx_axi_i2c_transfer;
|
|
bus->base.set_clock = xilinx_axi_i2c_set_clock;
|
|
bus->base.destroy = xilinx_axi_i2c_destroy;
|
|
|
|
axi_i2c_bus = bus;
|
|
|
|
return i2c_bus_register( &bus->base, bus_path );
|
|
}
|