blob: e99b7413984f9e73ccd5cf67f42984968100f66d [file] [log] [blame]
/*
* Copyright 2023 Google LLC
* Copyright lowRISC contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "sw/device/lib/testing/i2c_testutils.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include "hw/top_matcha/sw/autogen/top_matcha.h"
#include "i2c_regs.h" // Generated.
#include "sw/device/lib/base/mmio.h"
#include "sw/device/lib/dif/dif_i2c.h"
#include "sw/device/lib/dif/dif_pinmux.h"
#include "sw/device/lib/runtime/hart.h"
#include "sw/device/lib/runtime/log.h"
#include "sw/device/lib/testing/test_framework/check.h"
static const uint8_t kI2cWrite = 0;
static const uint8_t kI2cRead = 1;
// Default flags for i2c operations.
static const dif_i2c_fmt_flags_t kDefaultFlags = {.start = false,
.stop = false,
.read = false,
.read_cont = false,
.suppress_nak_irq = false};
typedef struct i2c_connect_conf {
const top_matcha_pinmux_mio_out_t pins_mio_out[2];
const top_matcha_pinmux_insel_t pins_insel[2];
const top_matcha_pinmux_peripheral_in_t pins_peripheral_in[2];
const top_matcha_pinmux_outsel_t pins_outsel[2];
} i2c_connect_conf_t;
const i2c_connect_conf_t i2c_conf[] = {
{.pins_mio_out = {kTopMatchaPinmuxMioOutIoa7, kTopMatchaPinmuxMioOutIoa8},
.pins_insel = {kTopMatchaPinmuxInselIoa7, kTopMatchaPinmuxInselIoa8},
.pins_peripheral_in = {kTopMatchaPinmuxPeripheralInI2c0Scl,
kTopMatchaPinmuxPeripheralInI2c0Sda},
.pins_outsel = {kTopMatchaPinmuxOutselI2c0Scl,
kTopMatchaPinmuxOutselI2c0Sda}},
{.pins_mio_out = {kTopMatchaPinmuxMioOutIob9, kTopMatchaPinmuxMioOutIob10},
.pins_insel = {kTopMatchaPinmuxInselIob9, kTopMatchaPinmuxInselIob10},
.pins_peripheral_in = {kTopMatchaPinmuxPeripheralInI2c1Scl,
kTopMatchaPinmuxPeripheralInI2c1Sda},
.pins_outsel = {kTopMatchaPinmuxOutselI2c1Scl,
kTopMatchaPinmuxOutselI2c1Sda}},
{.pins_mio_out = {kTopMatchaPinmuxMioOutIob11, kTopMatchaPinmuxMioOutIob12},
.pins_insel = {kTopMatchaPinmuxInselIob11, kTopMatchaPinmuxInselIob12},
.pins_peripheral_in = {kTopMatchaPinmuxPeripheralInI2c2Scl,
kTopMatchaPinmuxPeripheralInI2c2Sda},
.pins_outsel = {kTopMatchaPinmuxOutselI2c2Scl,
kTopMatchaPinmuxOutselI2c2Sda}}};
void i2c_testutils_wr(const dif_i2c_t *i2c, uint8_t addr, uint8_t byte_count,
const uint8_t *data, bool skip_stop) {
dif_i2c_fmt_flags_t flags = kDefaultFlags;
uint8_t data_frame;
// The current function does not support initializing a write while another
// transaction is in progress
// TODO: The current function does not support write payloads
// larger than the fifo depth.
CHECK(byte_count <= I2C_PARAM_FIFO_DEPTH);
// TODO: #15377 The I2C DIF says: "Callers should prefer
// `dif_i2c_write_byte()` instead, since that function provides clearer
// semantics. This function should only really be used for testing or
// troubleshooting a device.
// First write the address.
flags.start = true;
data_frame = (addr << 1) | kI2cWrite;
CHECK_DIF_OK(dif_i2c_write_byte_raw(i2c, data_frame, flags));
// Once address phase is through, blast the rest as generic data.
flags = kDefaultFlags;
for (uint8_t i = 0; i < byte_count; ++i) {
// Issue a stop for the last byte.
flags.stop = ((i == byte_count - 1) && !skip_stop);
CHECK_DIF_OK(dif_i2c_write_byte_raw(i2c, data[i], flags));
}
// TODO: Check for errors / status.
}
void i2c_testutils_rd(const dif_i2c_t *i2c, uint8_t addr, uint8_t byte_count) {
dif_i2c_fmt_flags_t flags = kDefaultFlags;
uint8_t data_frame;
uint8_t fifo_level;
// The current function doesn't check for space in the FIFOs
// First write the address.
flags.start = true;
data_frame = (addr << 1) | kI2cRead;
CHECK_DIF_OK(dif_i2c_write_byte_raw(i2c, data_frame, flags));
// Schedule the read transaction by writing flags to the fifo.
flags = kDefaultFlags;
flags.read = true;
flags.stop = true;
// Inform the controller how many bytes to read overall.
CHECK_DIF_OK(dif_i2c_write_byte_raw(i2c, byte_count, flags));
// TODO: Check for errors / status.
}
bool i2c_testutils_target_check_start(const dif_i2c_t *i2c, uint8_t *addr) {
uint8_t acq_fifo_lvl;
CHECK_DIF_OK(dif_i2c_get_fifo_levels(i2c, NULL, NULL, NULL, &acq_fifo_lvl));
CHECK(acq_fifo_lvl > 1);
dif_i2c_signal_t signal;
uint8_t byte;
CHECK_DIF_OK(dif_i2c_acquire_byte(i2c, &byte, &signal));
// Check acq_fifo is as expected and write addr and continue
CHECK(signal == kDifI2cSignalStart);
*addr = byte >> 1;
return byte & kI2cRead;
}
bool i2c_testutils_target_check_end(const dif_i2c_t *i2c, uint8_t *cont_byte) {
uint8_t acq_fifo_lvl;
CHECK_DIF_OK(dif_i2c_get_fifo_levels(i2c, NULL, NULL, NULL, &acq_fifo_lvl));
CHECK(acq_fifo_lvl >= 1);
dif_i2c_signal_t signal;
uint8_t byte;
CHECK_DIF_OK(dif_i2c_acquire_byte(i2c, &byte, &signal));
// Check transaction is terminated with a stop or a continue that the caller
// is prepared to handle
if (signal == kDifI2cSignalStop) {
return false;
}
CHECK(cont_byte != NULL);
*cont_byte = byte;
CHECK(signal == kDifI2cSignalRepeat);
return true;
}
void i2c_testutils_target_rd(const dif_i2c_t *i2c, uint8_t byte_count,
const uint8_t *data) {
uint8_t tx_fifo_lvl, acq_fifo_lvl;
CHECK_DIF_OK(
dif_i2c_get_fifo_levels(i2c, NULL, NULL, &tx_fifo_lvl, &acq_fifo_lvl));
// Check there's space in tx_fifo and acq_fifo
CHECK(tx_fifo_lvl + byte_count <= I2C_PARAM_FIFO_DEPTH);
CHECK(acq_fifo_lvl + 2 <= I2C_PARAM_FIFO_DEPTH);
for (uint8_t i = 0; i < byte_count; ++i) {
CHECK_DIF_OK(dif_i2c_transmit_byte(i2c, data[i]));
}
// TODO: Check for errors / status.
}
bool i2c_testutils_target_check_rd(const dif_i2c_t *i2c, uint8_t *addr,
uint8_t *cont_byte) {
CHECK(i2c_testutils_target_check_start(i2c, addr) == kI2cRead);
// TODO: Check for errors / status.
return i2c_testutils_target_check_end(i2c, cont_byte);
}
void i2c_testutils_target_wr(const dif_i2c_t *i2c, uint8_t byte_count) {
uint8_t acq_fifo_lvl;
CHECK_DIF_OK(dif_i2c_get_fifo_levels(i2c, NULL, NULL, NULL, &acq_fifo_lvl));
CHECK(acq_fifo_lvl + 2 + byte_count <= I2C_PARAM_FIFO_DEPTH);
// TODO: Check for errors / status.
}
bool i2c_testutils_target_check_wr(const dif_i2c_t *i2c, uint8_t byte_count,
uint8_t *addr, uint8_t *bytes,
uint8_t *cont_byte) {
uint8_t acq_fifo_lvl;
CHECK_DIF_OK(dif_i2c_get_fifo_levels(i2c, NULL, NULL, NULL, &acq_fifo_lvl));
CHECK(acq_fifo_lvl >= 2 + byte_count);
CHECK(i2c_testutils_target_check_start(i2c, addr) == kI2cWrite);
for (uint8_t i = 0; i < byte_count; ++i) {
dif_i2c_signal_t signal;
CHECK_DIF_OK(dif_i2c_acquire_byte(i2c, bytes + i, &signal));
CHECK(signal == kDifI2cSignalNone);
}
// TODO: Check for errors / status.
return i2c_testutils_target_check_end(i2c, cont_byte);
}
void i2c_testutils_connect_i2c_to_pinmux_pins(const dif_pinmux_t *pinmux,
uint8_t kI2cIdx) {
top_matcha_pinmux_mio_out_t i2c_pinmux_out1_id, i2c_pinmux_out2_id;
top_matcha_pinmux_insel_t i2c_pinmux_in1_id, i2c_pinmux_in2_id;
top_matcha_pinmux_peripheral_in_t i2c_pinmux_insel_scl_id,
i2c_pinmux_insel_sda_id;
top_matcha_pinmux_outsel_t i2c_pinmux_outsel_scl_id, i2c_pinmux_outsel_sda_id;
CHECK_DIF_OK(dif_pinmux_input_select(pinmux,
i2c_conf[kI2cIdx].pins_peripheral_in[0],
i2c_conf[kI2cIdx].pins_insel[0]));
CHECK_DIF_OK(dif_pinmux_input_select(pinmux,
i2c_conf[kI2cIdx].pins_peripheral_in[1],
i2c_conf[kI2cIdx].pins_insel[1]));
CHECK_DIF_OK(dif_pinmux_output_select(pinmux,
i2c_conf[kI2cIdx].pins_mio_out[0],
i2c_conf[kI2cIdx].pins_outsel[0]));
CHECK_DIF_OK(dif_pinmux_output_select(pinmux,
i2c_conf[kI2cIdx].pins_mio_out[1],
i2c_conf[kI2cIdx].pins_outsel[1]));
}