Archived
1
0
This repo is archived. You can view files and clone it, but cannot push or open issues or pull requests.
controller/Scan/ISSILed/i2c.c
Jacob Alexander d1e969ce8f Various KType updates
- 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
2016-05-12 22:11:23 -07:00

390 lines
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 );
}