; ; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. ; ; SPDX-License-Identifier: BSD-3-Clause ; .program i2c .side_set 1 opt pindirs ; TX Encoding: ; | 15:10 | 9 | 8:1 | 0 | ; | Instr | Final | Data | NAK | ; ; If Instr has a value n > 0, then this FIFO word has no ; data payload, and the next n + 1 words will be executed as instructions. ; Otherwise, shift out the 8 data bits, followed by the ACK bit. ; ; The Instr mechanism allows stop/start/repstart sequences to be programmed ; by the processor, and then carried out by the state machine at defined points ; in the datastream. ; ; The "Final" field should be set for the final byte in a transfer. ; This tells the state machine to ignore a NAK: if this field is not ; set, then any NAK will cause the state machine to halt and interrupt. ; ; Autopull should be enabled, with a threshold of 16. ; Autopush should be enabled, with a threshold of 8. ; The TX FIFO should be accessed with halfword writes, to ensure ; the data is immediately available in the OSR. ; ; Pin mapping: ; - Input pin 0 is SDA, 1 is SCL (if clock stretching used) ; - Jump pin is SDA ; - Side-set pin 0 is SCL ; - Set pin 0 is SDA ; - OUT pin 0 is SDA ; - SCL must be SDA + 1 (for wait mapping) ; ; The OE outputs should be inverted in the system IO controls! ; (It's possible for the inversion to be done in this program, ; but costs 2 instructions: 1 for inversion, and one to cope ; with the side effect of the MOV on TX shift counter.) do_nack: jmp y-- entry_point ; Continue if NAK was expected irq wait 0 rel ; Otherwise stop, ask for help do_byte: set x, 7 ; Loop 8 times bitloop: out pindirs, 1 [7] ; Serialise write data (all-ones if reading) nop side 1 [2] ; SCL rising edge wait 1 pin, 1 [4] ; Allow clock to be stretched in pins, 1 [7] ; Sample read data in middle of SCL pulse jmp x-- bitloop side 0 [7] ; SCL falling edge ; Handle ACK pulse out pindirs, 1 [7] ; On reads, we provide the ACK. nop side 1 [7] ; SCL rising edge wait 1 pin, 1 [7] ; Allow clock to be stretched jmp pin do_nack side 0 [2] ; Test SDA for ACK/NAK, fall through if ACK public entry_point: .wrap_target out x, 6 ; Unpack Instr count out y, 1 ; Unpack the NAK ignore bit jmp !x do_byte ; Instr == 0, this is a data record. out null, 32 ; Instr > 0, remainder of this OSR is invalid do_exec: out exec, 16 ; Execute one instruction per FIFO word jmp x-- do_exec ; Repeat n + 1 times .wrap % c-sdk { #include "hardware/clocks.h" #include "hardware/gpio.h" static inline void i2c_program_init(PIO pio, uint sm, uint offset, uint pin_sda, uint pin_scl) { assert(pin_scl == pin_sda + 1); pio_sm_config c = i2c_program_get_default_config(offset); // IO mapping sm_config_set_out_pins(&c, pin_sda, 1); sm_config_set_set_pins(&c, pin_sda, 1); sm_config_set_in_pins(&c, pin_sda); sm_config_set_sideset_pins(&c, pin_scl); sm_config_set_jmp_pin(&c, pin_sda); sm_config_set_out_shift(&c, false, true, 16); sm_config_set_in_shift(&c, false, true, 8); float div = (float)clock_get_hz(clk_sys) / (32 * 100000); sm_config_set_clkdiv(&c, div); // Try to avoid glitching the bus while connecting the IOs. Get things set // up so that pin is driven down when PIO asserts OE low, and pulled up // otherwise. gpio_pull_up(pin_scl); gpio_pull_up(pin_sda); uint32_t both_pins = (1u << pin_sda) | (1u << pin_scl); pio_sm_set_pins_with_mask(pio, sm, both_pins, both_pins); pio_sm_set_pindirs_with_mask(pio, sm, both_pins, both_pins); pio_gpio_init(pio, pin_sda); gpio_set_oeover(pin_sda, GPIO_OVERRIDE_INVERT); pio_gpio_init(pio, pin_scl); gpio_set_oeover(pin_scl, GPIO_OVERRIDE_INVERT); pio_sm_set_pins_with_mask(pio, sm, 0, both_pins); // Clear IRQ flag before starting hw_clear_bits(&pio->inte0, 1u << sm); hw_clear_bits(&pio->inte1, 1u << sm); pio->irq = 1u << sm; // Configure and start SM pio_sm_init(pio, sm, offset + i2c_offset_entry_point, &c); pio_sm_set_enabled(pio, sm, true); } %} .program set_scl_sda .side_set 1 opt ; Assemble a table of instructions which software can select from, and pass ; into the FIFO, to issue START/STOP/RSTART. This isn't intended to be run as ; a complete program. set pindirs, 0 side 0 [7] ; SCL = 0, SDA = 0 set pindirs, 1 side 0 [7] ; SCL = 0, SDA = 1 set pindirs, 0 side 1 [7] ; SCL = 1, SDA = 0 set pindirs, 1 side 1 [7] ; SCL = 1, SDA = 1 % c-sdk { // Define order of our instruction table enum { I2C_SC0_SD0 = 0, I2C_SC0_SD1, I2C_SC1_SD0, I2C_SC1_SD1 }; %}