148 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			148 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/**
 | 
						|
 * Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
 | 
						|
 *
 | 
						|
 * SPDX-License-Identifier: BSD-3-Clause
 | 
						|
 */
 | 
						|
 | 
						|
#include "pio_i2c.h"
 | 
						|
 | 
						|
const int PIO_I2C_ICOUNT_LSB = 10;
 | 
						|
const int PIO_I2C_FINAL_LSB  = 9;
 | 
						|
const int PIO_I2C_DATA_LSB   = 1;
 | 
						|
const int PIO_I2C_NAK_LSB    = 0;
 | 
						|
 | 
						|
 | 
						|
bool pio_i2c_check_error(PIO pio, uint sm) {
 | 
						|
    return !!(pio->irq & (1u << sm));
 | 
						|
}
 | 
						|
 | 
						|
void pio_i2c_resume_after_error(PIO pio, uint sm) {
 | 
						|
    pio_sm_drain_tx_fifo(pio, sm);
 | 
						|
    pio_sm_exec(pio, sm, (pio->sm[sm].execctrl & PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS) >> PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB);
 | 
						|
    pio->irq = 1u << sm;
 | 
						|
}
 | 
						|
 | 
						|
void pio_i2c_rx_enable(PIO pio, uint sm, bool en) {
 | 
						|
    if (en)
 | 
						|
        hw_set_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS);
 | 
						|
    else
 | 
						|
        hw_clear_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS);
 | 
						|
}
 | 
						|
 | 
						|
static inline void pio_i2c_put16(PIO pio, uint sm, uint16_t data) {
 | 
						|
    while (pio_sm_is_tx_fifo_full(pio, sm))
 | 
						|
        ;
 | 
						|
    // some versions of GCC dislike this
 | 
						|
#pragma GCC diagnostic push
 | 
						|
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
 | 
						|
    *(io_rw_16 *)&pio->txf[sm] = data;
 | 
						|
#pragma GCC diagnostic pop
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
// If I2C is ok, block and push data. Otherwise fall straight through.
 | 
						|
void pio_i2c_put_or_err(PIO pio, uint sm, uint16_t data) {
 | 
						|
    while (pio_sm_is_tx_fifo_full(pio, sm))
 | 
						|
        if (pio_i2c_check_error(pio, sm))
 | 
						|
            return;
 | 
						|
    if (pio_i2c_check_error(pio, sm))
 | 
						|
        return;
 | 
						|
    // some versions of GCC dislike this
 | 
						|
#pragma GCC diagnostic push
 | 
						|
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
 | 
						|
    *(io_rw_16 *)&pio->txf[sm] = data;
 | 
						|
#pragma GCC diagnostic pop
 | 
						|
}
 | 
						|
 | 
						|
uint8_t pio_i2c_get(PIO pio, uint sm) {
 | 
						|
    return (uint8_t)pio_sm_get(pio, sm);
 | 
						|
}
 | 
						|
 | 
						|
void pio_i2c_start(PIO pio, uint sm) {
 | 
						|
    pio_i2c_put_or_err(pio, sm, 1u << PIO_I2C_ICOUNT_LSB); // Escape code for 2 instruction sequence
 | 
						|
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]);    // We are already in idle state, just pull SDA low
 | 
						|
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]);    // Also pull clock low so we can present data
 | 
						|
}
 | 
						|
 | 
						|
void pio_i2c_stop(PIO pio, uint sm) {
 | 
						|
    pio_i2c_put_or_err(pio, sm, 2u << PIO_I2C_ICOUNT_LSB);
 | 
						|
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]);    // SDA is unknown; pull it down
 | 
						|
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]);    // Release clock
 | 
						|
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD1]);    // Release SDA to return to idle state
 | 
						|
};
 | 
						|
 | 
						|
void pio_i2c_repstart(PIO pio, uint sm) {
 | 
						|
    pio_i2c_put_or_err(pio, sm, 3u << PIO_I2C_ICOUNT_LSB);
 | 
						|
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD1]);
 | 
						|
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD1]);
 | 
						|
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]);
 | 
						|
    pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]);
 | 
						|
}
 | 
						|
 | 
						|
static void pio_i2c_wait_idle(PIO pio, uint sm) {
 | 
						|
    // Finished when TX runs dry or SM hits an IRQ
 | 
						|
    pio->fdebug = 1u << (PIO_FDEBUG_TXSTALL_LSB + sm);
 | 
						|
    while (!(pio->fdebug & 1u << (PIO_FDEBUG_TXSTALL_LSB + sm) || pio_i2c_check_error(pio, sm)))
 | 
						|
        tight_loop_contents();
 | 
						|
}
 | 
						|
 | 
						|
int pio_i2c_write_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *txbuf, uint len) {
 | 
						|
    int err = 0;
 | 
						|
    pio_i2c_start(pio, sm);
 | 
						|
    pio_i2c_rx_enable(pio, sm, false);
 | 
						|
    pio_i2c_put16(pio, sm, (addr << 2) | 1u);
 | 
						|
    while (len && !pio_i2c_check_error(pio, sm)) {
 | 
						|
        if (!pio_sm_is_tx_fifo_full(pio, sm)) {
 | 
						|
            --len;
 | 
						|
            pio_i2c_put_or_err(pio, sm, (*txbuf++ << PIO_I2C_DATA_LSB) | ((len == 0) << PIO_I2C_FINAL_LSB) | 1u);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    pio_i2c_stop(pio, sm);
 | 
						|
    pio_i2c_wait_idle(pio, sm);
 | 
						|
    if (pio_i2c_check_error(pio, sm)) {
 | 
						|
        err = -1;
 | 
						|
        pio_i2c_resume_after_error(pio, sm);
 | 
						|
        pio_i2c_stop(pio, sm);
 | 
						|
    }
 | 
						|
    return err;
 | 
						|
}
 | 
						|
 | 
						|
int pio_i2c_read_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *rxbuf, uint len) {
 | 
						|
    int err = 0;
 | 
						|
    pio_i2c_start(pio, sm);
 | 
						|
    pio_i2c_rx_enable(pio, sm, true);
 | 
						|
    while (!pio_sm_is_rx_fifo_empty(pio, sm))
 | 
						|
        (void)pio_i2c_get(pio, sm);
 | 
						|
    pio_i2c_put16(pio, sm, (addr << 2) | 3u);
 | 
						|
    uint32_t tx_remain = len; // Need to stuff 0xff bytes in to get clocks
 | 
						|
 | 
						|
    bool first = true;
 | 
						|
 | 
						|
    while ((tx_remain || len) && !pio_i2c_check_error(pio, sm)) {
 | 
						|
        if (tx_remain && !pio_sm_is_tx_fifo_full(pio, sm)) {
 | 
						|
            --tx_remain;
 | 
						|
            pio_i2c_put16(pio, sm, (0xffu << 1) | (tx_remain ? 0 : (1u << PIO_I2C_FINAL_LSB) | (1u << PIO_I2C_NAK_LSB)));
 | 
						|
        }
 | 
						|
        if (!pio_sm_is_rx_fifo_empty(pio, sm)) {
 | 
						|
            if (first) {
 | 
						|
                // Ignore returned address byte
 | 
						|
                (void)pio_i2c_get(pio, sm);
 | 
						|
                first = false;
 | 
						|
            }
 | 
						|
            else {
 | 
						|
                --len;
 | 
						|
                *rxbuf++ = pio_i2c_get(pio, sm);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    pio_i2c_stop(pio, sm);
 | 
						|
    pio_i2c_wait_idle(pio, sm);
 | 
						|
    if (pio_i2c_check_error(pio, sm)) {
 | 
						|
        err = -1;
 | 
						|
        pio_i2c_resume_after_error(pio, sm);
 | 
						|
        pio_i2c_stop(pio, sm);
 | 
						|
    }
 | 
						|
    return err;
 | 
						|
}
 | 
						|
 |