/** * @file * * @ingroup arm_beagle * * @brief BeagleBoard I2C bus initialization and API Support. */ /* * Copyright (c) 2016 Punit Vara * Copyright (c) 2017 Sichen Zhao * * 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 works, currently * the i2c file is working on the Beaglebone Black board(AM335x) */ #include #include #include #include #include #include #include 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 ); (void) no_bytes; /* indicate we know that no_bytes is not referenced again */ 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 & 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 ); }