diff options
Diffstat (limited to 'c/src/lib/libbsp/arm/beagle/i2c/bbb-i2c.c')
-rw-r--r-- | c/src/lib/libbsp/arm/beagle/i2c/bbb-i2c.c | 461 |
1 files changed, 461 insertions, 0 deletions
diff --git a/c/src/lib/libbsp/arm/beagle/i2c/bbb-i2c.c b/c/src/lib/libbsp/arm/beagle/i2c/bbb-i2c.c new file mode 100644 index 0000000000..3d7d820511 --- /dev/null +++ b/c/src/lib/libbsp/arm/beagle/i2c/bbb-i2c.c @@ -0,0 +1,461 @@ +/** + * @file + * + * @ingroup arm_beagle + * + * @brief BeagleBoard I2C bus initialization and API Support. + */ + +/* + * Copyright (c) 2017 Sichen Zhao <zsc19940506@gmail.com> + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.org/license/LICENSE. + */ + +/* + * Modified on Punit Vara<punitvara@gmail.com> works, currently + * the i2c file is working on the Beaglebone Black board(AM335x) + */ + +#include <stdio.h> +#include <bsp/i2c.h> +#include <libcpu/am335x.h> +#include <rtems/irq-extension.h> +#include <rtems/counter.h> +#include <bsp/bbb-gpio.h> +#include <rtems/score/assert.h> + +static void am335x_i2c0_pinmux( bbb_i2c_bus *bus ) +{ + REG( bus->regs + AM335X_CONF_I2C0_SDA ) = + ( BBB_RXACTIVE | BBB_SLEWCTRL | BBB_PU_EN ); + + REG( bus->regs + AM335X_CONF_I2C0_SCL ) = + ( BBB_RXACTIVE | BBB_SLEWCTRL | BBB_PU_EN ); +} + +static void I2C0ModuleClkConfig( void ) +{ + /* Writing to MODULEMODE field of AM335X_CM_WKUP_I2C0_CLKCTRL register. */ + REG( AM335X_SOC_CM_WKUP_REGS + AM335X_CM_WKUP_I2C0_CLKCTRL ) |= + AM335X_CM_WKUP_I2C0_CLKCTRL_MODULEMODE_ENABLE; + + /* Waiting for MODULEMODE field to reflect the written value. */ + while ( AM335X_CM_WKUP_I2C0_CLKCTRL_MODULEMODE_ENABLE != + ( REG( AM335X_SOC_CM_WKUP_REGS + AM335X_CM_WKUP_I2C0_CLKCTRL ) & + AM335X_CM_WKUP_I2C0_CLKCTRL_MODULEMODE ) ) ; + + /* + ** Waiting for IDLEST field in AM335X_CM_WKUP_CONTROL_CLKCTRL + ** register to attain + ** desired value. + */ + while ( ( AM335X_CM_WKUP_CONTROL_CLKCTRL_IDLEST_FUNC << + AM335X_CM_WKUP_CONTROL_CLKCTRL_IDLEST_SHIFT ) != + ( REG( AM335X_SOC_CM_WKUP_REGS + AM335X_CM_WKUP_CONTROL_CLKCTRL ) & + AM335X_CM_WKUP_CONTROL_CLKCTRL_IDLEST ) ) ; + + /* + ** Waiting for CLKACTIVITY_I2C0_GFCLK field in AM335X_CM_WKUP_CLKSTCTRL + ** register to attain desired value. + */ + while ( AM335X_CM_WKUP_CLKSTCTRL_CLKACTIVITY_I2C0_GFCLK != + ( REG( AM335X_SOC_CM_WKUP_REGS + AM335X_CM_WKUP_CLKSTCTRL ) & + AM335X_CM_WKUP_CLKSTCTRL_CLKACTIVITY_I2C0_GFCLK ) ) ; + + /* + ** Waiting for IDLEST field in AM335X_CM_WKUP_I2C0_CLKCTRL register to attain + ** desired value. + */ + while ( ( AM335X_CM_WKUP_I2C0_CLKCTRL_IDLEST_FUNC << + AM335X_CM_WKUP_I2C0_CLKCTRL_IDLEST_SHIFT ) != + ( REG( AM335X_SOC_CM_WKUP_REGS + AM335X_CM_WKUP_I2C0_CLKCTRL ) & + AM335X_CM_WKUP_I2C0_CLKCTRL_IDLEST ) ) ; +} + +static void am335x_i2c_reset( bbb_i2c_bus *bus ) +{ + volatile bbb_i2c_regs *regs = bus->regs; + int timeout = I2C_TIMEOUT; + + if ( REG( ®s->BBB_I2C_CON ) & BBB_I2C_CON_EN ) { + REG( ®s->BBB_I2C_CON ) = BBB_I2C_CON_CLR; + udelay( 50000 ); + } + + REG( ®s->BBB_I2C_SYSC ) = BBB_I2C_SYSC_SRST; /* for ES2 after soft reset */ + udelay( 1000 ); + REG( ®s->BBB_I2C_CON ) = BBB_I2C_CON_EN; + + while ( !( REG( ®s->BBB_I2C_SYSS ) & BBB_I2C_SYSS_RDONE ) && timeout-- ) { + if ( timeout <= 0 ) { + puts( "ERROR: Timeout in soft-reset\n" ); + + return; + } + + udelay( 1000 ); + } +} +/* + Possible values for msg->flag + * - @ref I2C_M_TEN, + * - @ref I2C_M_RD, + * - @ref I2C_M_STOP, + * - @ref I2C_M_NOSTART, + * - @ref I2C_M_REV_DIR_ADDR, + * - @ref I2C_M_IGNORE_NAK, + * - @ref I2C_M_NO_RD_ACK, and + * - @ref I2C_M_RECV_LEN. + */ + +static void am335x_i2c_set_address_size( + const i2c_msg *msgs, + volatile bbb_i2c_regs *regs +) +{ + /*can be configured multiple modes here. + **Need to think about own address modes*/ + if ( ( msgs->flags & I2C_M_TEN ) == 0 ) { + /* 7-bit mode slave address mode*/ + REG( ®s->BBB_I2C_CON ) = AM335X_I2C_CFG_7BIT_SLAVE_ADDR; + } else { + /* 10-bit slave address mode*/ + REG( ®s->BBB_I2C_CON ) = AM335X_I2C_CFG_10BIT_SLAVE_ADDR; + } +} + +static void am335x_i2c_next_byte( bbb_i2c_bus *bus ) +{ + i2c_msg *msg; + + ++bus->msgs; + --bus->msg_todo; + msg = &bus->msgs[ 0 ]; + bus->current_msg_todo = msg->len; + bus->current_msg_byte = msg->buf; +} + +static void am335x_i2c_masterint_enable( + volatile bbb_i2c_regs *regs, + unsigned int flag +) +{ + REG( ®s->BBB_I2C_IRQENABLE_SET ) |= flag; +} + +static void am335x_i2c_masterint_disable( + volatile bbb_i2c_regs *regs, + unsigned int flag +) +{ + REG( ®s->BBB_I2C_IRQENABLE_CLR ) = flag; +} + +static void am335x_int_clear( + volatile bbb_i2c_regs *regs, + unsigned int flag +) +{ + REG( ®s->BBB_I2C_IRQSTATUS ) = flag; +} + +static void am335x_clean_interrupts( volatile bbb_i2c_regs *regs ) +{ + am335x_i2c_masterint_enable( regs, BBB_I2C_ALL_FLAGS ); + am335x_int_clear( regs, BBB_I2C_ALL_FLAGS ); + am335x_i2c_masterint_disable( regs, BBB_I2C_ALL_FLAGS ); +} + +static void am335x_i2c_setup_read_transfer( + bbb_i2c_bus *bus, + volatile bbb_i2c_regs *regs, + const i2c_msg *msgs, + bool send_stop +) +{ + REG( ®s->BBB_I2C_CNT ) = bus->current_msg_todo; + + REG( ®s->BBB_I2C_CON ) = AM335X_I2C_CFG_MST_RX | AM335X_I2C_CON_I2C_EN; + + if ( send_stop ) { + REG( ®s->BBB_I2C_CON ) |= AM335X_I2C_CON_START | AM335X_I2C_CON_STOP; + } else { + REG( ®s->BBB_I2C_CON ) |= AM335X_I2C_CON_START; + } + + am335x_i2c_masterint_enable( regs, AM335X_I2C_INT_RECV_READY | + AM335X_I2C_IRQSTATUS_ARDY ); +} + +static void am335x_i2c_continue_read_transfer( + bbb_i2c_bus *bus, + volatile bbb_i2c_regs *regs +) +{ + bus->current_msg_byte[ bus->already_transferred ] = + REG( ®s->BBB_I2C_DATA ); + + bus->already_transferred++; + + REG( ®s->BBB_I2C_IRQSTATUS ) = AM335X_I2C_INT_RECV_READY; + + if ( bus->already_transferred == bus->current_msg_todo - 1 ) { + REG( ®s->BBB_I2C_CON ) |= AM335X_I2C_CON_STOP; + } +} + +static void am335x_i2c_continue_write( + bbb_i2c_bus *bus, + volatile bbb_i2c_regs *regs +) +{ + if ( bus->already_transferred == bus->msg_todo ) { + REG( ®s->BBB_I2C_DATA ) = + bus->current_msg_byte[ bus->already_transferred ]; + REG( ®s->BBB_I2C_IRQSTATUS ) = AM335X_I2C_IRQSTATUS_XRDY; + am335x_i2c_masterint_disable( regs, AM335X_I2C_IRQSTATUS_XRDY ); + REG( ®s->BBB_I2C_CON ) |= AM335X_I2C_CON_STOP; + } else { + writeb( bus->current_msg_byte[ bus->already_transferred ], + ®s->BBB_I2C_DATA ); + REG( ®s->BBB_I2C_IRQSTATUS ) = AM335X_I2C_IRQSTATUS_XRDY; + bus->already_transferred++; + } +} + +static void am335x_i2c_setup_write_transfer( + bbb_i2c_bus *bus, + volatile bbb_i2c_regs *regs, + const i2c_msg *msgs +) +{ + volatile unsigned int no_bytes; + + REG( ®s->BBB_I2C_CNT ) = bus->current_msg_todo; + no_bytes = REG( ®s->BBB_I2C_CNT ); + REG( ®s->BBB_I2C_SA ) = msgs->addr; + REG( ®s->BBB_I2C_CON ) = AM335X_I2C_CFG_MST_TX | AM335X_I2C_CON_I2C_EN; + am335x_clean_interrupts( regs ); + am335x_i2c_masterint_enable( regs, AM335X_I2C_IRQSTATUS_XRDY ); + REG( ®s->BBB_I2C_CON ) |= AM335X_I2C_CON_START | AM335X_I2C_CON_STOP; +} + +static void am335x_i2c_setup_transfer( + bbb_i2c_bus *bus, + volatile bbb_i2c_regs *regs +) +{ + const i2c_msg *msgs = bus->msgs; + uint32_t msg_todo = bus->msg_todo; + bool send_stop = false; + uint32_t i; + + 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; + } + + regs = bus->regs; + REG( &bus->regs->BBB_I2C_BUF ) |= AM335X_I2C_BUF_TXFIFO_CLR; + REG( &bus->regs->BBB_I2C_BUF ) |= AM335X_I2C_BUF_RXFIFO_CLR; + am335x_i2c_set_address_size( msgs, regs ); + bus->read = ( msgs->flags & I2C_M_RD ) != 0; + bus->already_transferred = ( bus->read == true ) ? 0 : 1; + + if ( bus->read ) { + if ( bus->current_msg_todo == 1 ) { + send_stop = true; + } + + am335x_i2c_setup_read_transfer( bus, regs, msgs, send_stop ); + } else { + am335x_i2c_setup_write_transfer( bus, regs, msgs ); + } +} + +static void am335x_i2c_interrupt( void *arg ) +{ + bbb_i2c_bus *bus = arg; + volatile bbb_i2c_regs *regs = bus->regs; + /* get status of enabled interrupts */ + uint32_t irqstatus = REG( ®s->BBB_I2C_IRQSTATUS ); + bool done = false; + + /* Clear all enabled interrupt except receive ready + and transmit ready interrupt in status register */ + REG( ®s->BBB_I2C_IRQSTATUS ) = + ( irqstatus & ~( AM335X_I2C_IRQSTATUS_RRDY | + AM335X_I2C_IRQSTATUS_XRDY ) ); + + if ( irqstatus & AM335X_I2C_INT_RECV_READY ) { + am335x_i2c_continue_read_transfer( bus, regs ); + } + + if ( irqstatus & AM335X_I2C_IRQSTATUS_XRDY ) { + am335x_i2c_continue_write( bus, regs ); + } + + if ( irqstatus & AM335X_I2C_IRQSTATUS_NACK ) { + done = true; + am335x_i2c_masterint_disable( regs, AM335X_I2C_IRQSTATUS_NACK ); + } + + if ( irqstatus & AM335X_I2C_IRQSTATUS_ARDY ) { + done = true; + REG( ®s->BBB_I2C_IRQSTATUS ) = BBB_I2C_STAT_ARDY; + } + + if ( irqstatus & AM335X_I2C_IRQSTATUS_BF ) { + REG( ®s->BBB_I2C_IRQSTATUS ) = AM335X_I2C_IRQSTATUS_BF; + } + + if ( done ) { + uint32_t err = irqstatus & BBB_I2C_IRQ_ERROR; + am335x_i2c_next_byte( bus ); + + if ( bus->msg_todo == 0 ) { + rtems_status_code sc; + am335x_i2c_masterint_disable( regs, ( AM335X_I2C_IRQSTATUS_RRDY | + AM335X_I2C_IRQSTATUS_XRDY | + AM335X_I2C_IRQSTATUS_BF ) ); + REG( ®s->BBB_I2C_IRQSTATUS ) = err; + + sc = rtems_event_transient_send( bus->task_id ); + _Assert( sc == RTEMS_SUCCESSFUL ); + (void) sc; + } else { + am335x_i2c_setup_transfer( bus, regs ); + } + } +} + +static int am335x_i2c_transfer( + i2c_bus *base, + i2c_msg *msgs, + uint32_t msg_count +) +{ + rtems_status_code sc; + bbb_i2c_bus *bus = (bbb_i2c_bus *) base; + volatile bbb_i2c_regs *regs; + uint32_t i; + + rtems_task_wake_after( 1 ); + + if ( msg_count < 1 ) { + return 1; + } + + for ( i = 0; i < msg_count; ++i ) { + if ( ( msgs[ i ].flags & I2C_M_RECV_LEN ) != 0 ) { + return -EINVAL; + } + } + + bus->msgs = &msgs[ 0 ]; + bus->msg_todo = msg_count; + bus->current_msg_todo = msgs[ 0 ].len; + bus->current_msg_byte = msgs[ 0 ].buf; + bus->task_id = rtems_task_self(); + regs = bus->regs; + am335x_i2c_setup_transfer( bus, regs ); + REG( ®s->BBB_I2C_IRQENABLE_SET ) = BBB_I2C_IRQ_USED; + + sc = rtems_event_transient_receive( RTEMS_WAIT, bus->base.timeout ); + + if ( sc != RTEMS_SUCCESSFUL ) { + am335x_i2c_reset( bus ); + rtems_event_transient_clear(); + + return -ETIMEDOUT; + } + + return 0; +} + +static int am335x_i2c_set_clock( + i2c_bus *base, + unsigned long clock +) +{ + bbb_i2c_bus *bus = (bbb_i2c_bus *) base; + uint32_t prescaler, divider; + + prescaler = ( BBB_I2C_SYSCLK / BBB_I2C_INTERNAL_CLK ) - 1; + REG( &bus->regs->BBB_I2C_PSC ) = prescaler; + divider = BBB_I2C_INTERNAL_CLK / ( 2 * clock ); + REG( &bus->regs->BBB_I2C_SCLL ) = ( divider - 7 ); + REG( &bus->regs->BBB_I2C_SCLH ) = ( divider - 5 ); + + return 0; +} + +static void am335x_i2c_destroy( i2c_bus *base ) +{ + bbb_i2c_bus *bus = (bbb_i2c_bus *) base; + rtems_status_code sc; + + sc = rtems_interrupt_handler_remove( bus->irq, am335x_i2c_interrupt, bus ); + _Assert( sc == RTEMS_SUCCESSFUL ); + (void) sc; + i2c_bus_destroy_and_free( &bus->base ); +} + +int am335x_i2c_bus_register( + const char *bus_path, + uintptr_t register_base, + uint32_t input_clock, + rtems_vector_number irq +) +{ + bbb_i2c_bus *bus; + rtems_status_code sc; + int err; + + /*check bus number is >0 & <MAX*/ + bus = (bbb_i2c_bus *) i2c_bus_alloc_and_init( sizeof( *bus ) ); + + if ( bus == NULL ) { + return -1; + } + + bus->regs = (volatile bbb_i2c_regs *) register_base; + + I2C0ModuleClkConfig(); + am335x_i2c0_pinmux( bus ); + am335x_i2c_reset( bus ); + bus->input_clock = input_clock; + err = am335x_i2c_set_clock( &bus->base, I2C_BUS_CLOCK_DEFAULT ); + + if ( err != 0 ) { + ( *bus->base.destroy )( &bus->base ); + rtems_set_errno_and_return_minus_one( -err ); + } + + bus->irq = irq; + REG( &bus->regs->BBB_I2C_IRQSTATUS ) = BBB_I2C_ALL_IRQ_FLAGS; + + sc = rtems_interrupt_handler_install( + irq, + "BBB_I2C", + RTEMS_INTERRUPT_UNIQUE, + (rtems_interrupt_handler) am335x_i2c_interrupt, + bus + ); + + if ( sc != RTEMS_SUCCESSFUL ) { + ( *bus->base.destroy )( &bus->base ); + rtems_set_errno_and_return_minus_one( EIO ); + } + + bus->base.transfer = am335x_i2c_transfer; + bus->base.set_clock = am335x_i2c_set_clock; + bus->base.destroy = am335x_i2c_destroy; + + return i2c_bus_register( &bus->base, bus_path ); +}
\ No newline at end of file |