123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- /*
- * Copyright (C) 2014 Jan Rychter
- * Modifications (C) 2015-2016 Jacob Alexander
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files ( the "Software" ), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
- // ----- Includes ----
-
- // Compiler Includes
- #include <Lib/ScanLib.h>
-
- // Project Includes
- #include <print.h>
- #include <kll_defs.h>
-
- // Local Includes
- #include "i2c.h"
-
-
-
- // ----- Variables -----
-
- volatile I2C_Channel i2c_channels[ISSI_I2C_Buses_define];
-
- uint32_t i2c_offset[] = {
- 0x0, // Bus 0
- 0x1000, // Bus 1
- };
-
-
-
- // ----- Functions -----
-
- inline void i2c_setup()
- {
- for ( uint8_t ch = 0; ch < ISSI_I2C_Buses_define; ch++ )
- {
- volatile uint8_t *I2C_F = (uint8_t*)(&I2C0_F) + i2c_offset[ch];
- volatile uint8_t *I2C_FLT = (uint8_t*)(&I2C0_FLT) + i2c_offset[ch];
- volatile uint8_t *I2C_C1 = (uint8_t*)(&I2C0_C1) + i2c_offset[ch];
- volatile uint8_t *I2C_C2 = (uint8_t*)(&I2C0_C2) + i2c_offset[ch];
-
- switch ( ch )
- {
- case 0:
- // Enable I2C internal clock
- SIM_SCGC4 |= SIM_SCGC4_I2C0; // Bus 0
-
- // External pull-up resistor
- PORTB_PCR0 = PORT_PCR_ODE | PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(2);
- PORTB_PCR1 = PORT_PCR_ODE | PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(2);
-
- break;
-
- case 1:
- // Enable I2C internal clock
- SIM_SCGC4 |= SIM_SCGC4_I2C1; // Bus 1
-
- // External pull-up resistor
- PORTC_PCR10 = PORT_PCR_ODE | PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(2);
- PORTC_PCR11 = PORT_PCR_ODE | PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(2);
-
- break;
- }
-
- // SCL Frequency Divider
- // 1.8 MBaud ( likely higher than spec )
- // 0x82 -> 36 MHz / (4 * 3) = 2.25 MBaud
- // 0x80 => mul(4)
- // 0x05 => ICL(5)
- *I2C_F = 0x84;
- *I2C_FLT = 4;
- *I2C_C1 = I2C_C1_IICEN;
- *I2C_C2 = I2C_C2_HDRS; // High drive select
-
- switch ( ch )
- {
- case 0:
- // Enable I2C Interrupt
- NVIC_ENABLE_IRQ( IRQ_I2C0 );
- break;
-
- case 1:
-
- // Enable I2C Interrupt
- NVIC_ENABLE_IRQ( IRQ_I2C1 );
- break;
- }
- }
- }
-
- uint8_t i2c_busy( uint8_t ch )
- {
- volatile I2C_Channel *channel = &( i2c_channels[ch] );
- if ( channel->status == I2C_BUSY )
- {
- return 1;
- }
-
- return 0;
- }
-
- uint8_t i2c_any_busy()
- {
- for ( uint8_t ch = 0; ch < ISSI_I2C_Buses_define; ch++ )
- {
- if ( i2c_busy( ch ) )
- return 1;
- }
- return 0;
- }
-
-
- // These are here for readability and correspond to bit 0 of the address byte.
- #define I2C_WRITING 0
- #define I2C_READING 1
-
- int32_t i2c_send_sequence(
- uint8_t ch,
- uint16_t *sequence,
- uint32_t sequence_length,
- uint8_t *received_data,
- void ( *callback_fn )( void* ),
- void *user_data
- ) {
- volatile I2C_Channel *channel = &( i2c_channels[ch] );
-
- volatile uint8_t *I2C_C1 = (uint8_t*)(&I2C0_C1) + i2c_offset[ch];
- volatile uint8_t *I2C_S = (uint8_t*)(&I2C0_S) + i2c_offset[ch];
- volatile uint8_t *I2C_D = (uint8_t*)(&I2C0_D) + i2c_offset[ch];
-
- int32_t result = 0;
- uint8_t status;
-
- if ( channel->status == I2C_BUSY )
- {
- return -1;
- }
-
- // Debug
- /*
- for ( uint8_t c = 0; c < sequence_length; c++ )
- {
- printHex( sequence[c] );
- print(" ");
- }
- print(NL);
- */
-
- channel->sequence = sequence;
- channel->sequence_end = sequence + sequence_length;
- channel->received_data = received_data;
- channel->status = I2C_BUSY;
- channel->txrx = I2C_WRITING;
- channel->callback_fn = callback_fn;
- channel->user_data = user_data;
-
- // reads_ahead does not need to be initialized
-
- // Acknowledge the interrupt request, just in case
- *I2C_S |= I2C_S_IICIF;
- *I2C_C1 = ( I2C_C1_IICEN | I2C_C1_IICIE );
-
- // Generate a start condition and prepare for transmitting.
- *I2C_C1 |= ( I2C_C1_MST | I2C_C1_TX );
-
- status = *I2C_S;
- if ( status & I2C_S_ARBL )
- {
- warn_print("Arbitration lost");
- result = -1;
- goto i2c_send_sequence_cleanup;
- }
-
- // Write the first (address) byte.
- *I2C_D = *channel->sequence++;
-
- // Everything is OK.
- return result;
-
- i2c_send_sequence_cleanup:
- *I2C_C1 &= ~( I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TX );
- channel->status = I2C_ERROR;
- return result;
- }
-
-
- void i2c_isr( uint8_t ch )
- {
- volatile I2C_Channel* channel = &i2c_channels[ch];
-
- volatile uint8_t *I2C_C1 = (uint8_t*)(&I2C0_C1) + i2c_offset[ch];
- volatile uint8_t *I2C_S = (uint8_t*)(&I2C0_S) + i2c_offset[ch];
- volatile uint8_t *I2C_D = (uint8_t*)(&I2C0_D) + i2c_offset[ch];
-
- uint8_t element;
- uint8_t status;
-
- status = *I2C_S;
-
- // Acknowledge the interrupt request
- *I2C_S |= I2C_S_IICIF;
-
- // Arbitration problem
- if ( status & I2C_S_ARBL )
- {
- warn_msg("Arbitration error. Bus: ");
- printHex( ch );
- print(NL);
-
- *I2C_S |= I2C_S_ARBL;
- goto i2c_isr_error;
- }
-
- if ( channel->txrx == I2C_READING )
- {
-
- switch( channel->reads_ahead )
- {
- // All the reads in the sequence have been processed ( but note that the final data register read still needs to
- // be done below! Now, the next thing is either a restart or the end of a sequence. In any case, we need to
- // switch to TX mode, either to generate a repeated start condition, or to avoid triggering another I2C read
- // when reading the contents of the data register.
- case 0:
- *I2C_C1 |= I2C_C1_TX;
-
- // Perform the final data register read now that it's safe to do so.
- *channel->received_data++ = *I2C_D;
-
- // Do we have a repeated start?
- if ( ( channel->sequence < channel->sequence_end ) && ( *channel->sequence == I2C_RESTART ) )
- {
-
- // Generate a repeated start condition.
- *I2C_C1 |= I2C_C1_RSTA;
-
- // A restart is processed immediately, so we need to get a new element from our sequence. This is safe, because
- // a sequence cannot end with a RESTART: there has to be something after it. Note that the only thing that can
- // come after a restart is an address write.
- channel->txrx = I2C_WRITING;
- channel->sequence++;
- element = *channel->sequence;
- *I2C_D = element;
- }
- else
- {
- goto i2c_isr_stop;
- }
- break;
-
- case 1:
- // do not ACK the final read
- *I2C_C1 |= I2C_C1_TXAK;
- *channel->received_data++ = *I2C_D;
- break;
-
- default:
- *channel->received_data++ = *I2C_D;
- break;
- }
-
- channel->reads_ahead--;
-
- }
- // channel->txrx == I2C_WRITING
- else
- {
- // First, check if we are at the end of a sequence.
- if ( channel->sequence == channel->sequence_end )
- goto i2c_isr_stop;
-
- // We received a NACK. Generate a STOP condition and abort.
- if ( status & I2C_S_RXAK )
- {
- warn_print("NACK Received");
- goto i2c_isr_error;
- }
-
- // check next thing in our sequence
- element = *channel->sequence;
-
- // Do we have a restart? If so, generate repeated start and make sure TX is on.
- if ( element == I2C_RESTART )
- {
- *I2C_C1 |= I2C_C1_RSTA | I2C_C1_TX;
-
- // A restart is processed immediately, so we need to get a new element from our sequence.
- // This is safe, because a sequence cannot end with a RESTART: there has to be something after it.
- channel->sequence++;
- element = *channel->sequence;
-
- // Note that the only thing that can come after a restart is a write.
- *I2C_D = element;
- }
- else
- {
- if ( element == I2C_READ ) {
- channel->txrx = I2C_READING;
- // How many reads do we have ahead of us ( not including this one )?
- // For reads we need to know the segment length to correctly plan NACK transmissions.
- // We already know about one read
- channel->reads_ahead = 1;
- while (
- ( ( channel->sequence + channel->reads_ahead ) < channel->sequence_end ) &&
- ( *( channel->sequence + channel->reads_ahead ) == I2C_READ )
- ) {
- channel->reads_ahead++;
- }
-
- // Switch to RX mode.
- *I2C_C1 &= ~I2C_C1_TX;
-
- // do not ACK the final read
- if ( channel->reads_ahead == 1 )
- {
- *I2C_C1 |= I2C_C1_TXAK;
- }
- // ACK all but the final read
- else
- {
- *I2C_C1 &= ~( I2C_C1_TXAK );
- }
-
- // Dummy read comes first, note that this is not valid data!
- // This only triggers a read, actual data will come in the next interrupt call and overwrite this.
- // This is why we do not increment the received_data pointer.
- *channel->received_data = *I2C_D;
- channel->reads_ahead--;
- }
- // Not a restart, not a read, must be a write.
- else
- {
- *I2C_D = element;
- }
- }
- }
-
- channel->sequence++;
- return;
-
- i2c_isr_stop:
- // Generate STOP ( set MST=0 ), switch to RX mode, and disable further interrupts.
- *I2C_C1 &= ~( I2C_C1_MST | I2C_C1_IICIE | I2C_C1_TXAK );
- channel->status = I2C_AVAILABLE;
-
- // Call the user-supplied callback function upon successful completion (if it exists).
- if ( channel->callback_fn )
- {
- // Delay 10 microseconds before starting linked function
- // TODO, is this chip dependent? -HaaTa
- delayMicroseconds(10);
- ( *channel->callback_fn )( channel->user_data );
- }
- return;
-
- i2c_isr_error:
- // Generate STOP and disable further interrupts.
- *I2C_C1 &= ~( I2C_C1_MST | I2C_C1_IICIE );
- channel->status = I2C_ERROR;
- return;
- }
-
- void i2c0_isr()
- {
- i2c_isr( 0 );
- }
-
- void i2c1_isr()
- {
- i2c_isr( 1 );
- }
|