d1e969ce8f
- Updated port switching pins (split USB and UART switching) - Added basic support for 2nd i2c bus - Updated key matrix - Fixed udev rules - Added missing register defines
390 linhas
9.6 KiB
C
390 linhas
9.6 KiB
C
/*
|
|
* 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 );
|
|
}
|
|
|