Skip to content

Commit

Permalink
Implement I2C DPI and device models
Browse files Browse the repository at this point in the history
Introduce a generic DPI for an I2C bus with an arbitrary number
of attached I2C devices. All bus speeds and device behaviours
including Sr (restart) conditions should be supported, and only
support for clock stretching and multiple controllers is presently
unimplemented.

Introduce simple device models for RPi Sense HAT ID EEPROM, RPi
Sense HAT IMU (`WHO_AM_I` registers only) and the AS621x Digital
Temperature Sensor that are presently anticipated and exercised
by the `sw/legacy/demo/i2c_hat_id` code.
  • Loading branch information
alees24 authored and HU90m committed Sep 25, 2024
1 parent 9c6bbf8 commit 80fcd12
Show file tree
Hide file tree
Showing 14 changed files with 885 additions and 15 deletions.
68 changes: 68 additions & 0 deletions dv/dpi/i2cdpi/i2c_as621x.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#include <stdlib.h>

#include "i2c_as621x.hh"

// Bus reset; reset the device state.
void i2c_as621x::busReset() {
i2cdevice::busReset();
index = 0u;
byteCount = 0u;
}

// Start condition occurred on the I2C bus (broadcast to all devices).
void i2c_as621x::startCond() {
i2cdevice::startCond();
byteCount = 0u;
}

// Write a byte of data to the AS612x Digital Temperature Sensor.
bool i2c_as621x::writeByte(uint8_t inByte, uint32_t oobIn) {
// Word write programs the Index register and then optionally the 16 bits of the register
// selected by the Index register.
switch (byteCount) {
case 0u:
if (!(index >> 2)) index = inByte & 3u;
break;
case 1u: dataHi = inByte; break;
case 2u: {
uint16_t val = ((uint16_t)dataHi << 8) | inByte;
switch (index) {
case 0u: break; // 'tval' register is Read Only
case 1u: config = val; break;
case 2u: tlow = val; break;
default: thigh = val; break;
}
}
break;
}
byteCount++;
// Byte accepted (send ACK).
return true;
}

// Read a byte of data from the AS612x Digital Temperature Sensor.
bool i2c_as621x::readByte(uint32_t oobIn, uint8_t &outByte, uint32_t &oobOut) {
// Collect the contents of the selected register; Word Read returns 16 bits from the register
// indicated by the Index register which must have been written already.
uint16_t val;
switch (index & 3u) {
case 0u: {
// Random fluctuations of temperature across the range 24-26 degrees.
val = 0xc00u + (rand() & 0xffu);
}
break;
case 1u: val = config; break;
case 2u: val = tlow; break;
default: val = thigh; break;
}

logText("AS621x returning 0x%04x from reg %u\n", val, index & 3u);
outByte = (byteCount & 1u) ? (uint8_t)val : (uint8_t)(val >> 8);
byteCount ^= 1u;
// Byte available, transmit to controller.
return true;
}
42 changes: 42 additions & 0 deletions dv/dpi/i2cdpi/i2c_as621x.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#include "i2cdevice.hh"

// Model of AS621x Digital Temperature Sensor.
class i2c_as621x : public i2cdevice {
public:
i2c_as621x(i2caddr_t addr) : i2cdevice(addr) {
busReset();
}

protected:
// Bus reset condition.
virtual void busReset();

// Start condition occurred on the I2C bus (broadcast to all devices).
virtual void startCond();

// Write a byte of data to the AS621x Digital Temperature Sensor.
virtual bool writeByte(uint8_t inByte, uint32_t oobIn);

// Read a byte of data from the AS621x Digital Temperature Sensor.
virtual bool readByte(uint32_t oobIn, uint8_t &outByte, uint32_t &oobOut);

private:
// Number of bytes transferred in current transcation.
uint8_t byteCount;

// High byte of two-byte write operation (the MS byte is transmitted first).
uint8_t dataHi;

// Current temperature value (tval) is not stored; value is generated when read.
// CONFIGuration register.
uint16_t config;
// TLOW and THIGH temperature threshold values.
uint16_t tlow;
uint16_t thigh;
// Index register selects amongt the 4 registers above.
uint8_t index;
};
86 changes: 86 additions & 0 deletions dv/dpi/i2cdpi/i2c_hat_id.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#include <assert.h>
#include <stdlib.h>
#include <string.h>

#include "i2c_hat_id.hh"

// Captured EEPROM ID header from Raspberry Pi Sense HAT; this device is not Read Only and it
// supports Byte/Page Writes too. This static signature is used to initialise the memory.
static const uint8_t id[] = {
0x52, 0x2D, 0x50, 0x69, 0x01, 0x00, 0x03, 0x00,
0xF0, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // R-Pi............
0x2D, 0x00, 0x00, 0x00, 0xE7, 0x58, 0xD1, 0x95,
0xF1, 0x56, 0x28, 0xAB, 0xCB, 0x4E, 0x99, 0x42, // -....X...V(..N.B
0xC7, 0x79, 0xD6, 0xA3, 0x01, 0x00, 0x01, 0x00,
0x0C, 0x09, 0x52, 0x61, 0x73, 0x70, 0x62, 0x65, // .y........Raspbe
0x72, 0x72, 0x79, 0x20, 0x50, 0x69, 0x53, 0x65,
0x6E, 0x73, 0x65, 0x20, 0x48, 0x41, 0x54, 0x7F, // rry PiSense HAT.
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // ................
};

// Both of these are physical device properties so they should not be changed anyway,
// but they must be powers of two for the logic to work.
static_assert(!(i2c_hat_id::kMemSize & (i2c_hat_id::kMemSize - 1u)));
static_assert(!(i2c_hat_id::kPageSize & (i2c_hat_id::kPageSize - 1u)));

// Constructor initialises the device state.
i2c_hat_id::i2c_hat_id(i2caddr_t addr) : i2cdevice(addr) {
busReset();
// Initialise the memory contents from the static signature above.
assert(sizeof(mem) >= sizeof(id));
memcpy(mem, id, sizeof(id));
}

// Bus reset condition; reset the device.
void i2c_hat_id::busReset() {
i2cdevice::busReset();
byteCount = 0u;
currAddr = 0u;
}

// Start condition occurred on the I2C bus (broadcast to all devices).
void i2c_hat_id::startCond() {
i2cdevice::startCond();
byteCount = 0u;
}

// Write a byte of data to the the RPi Sense HAT ID EEPROM.
bool i2c_hat_id::writeByte(uint8_t inByte, uint32_t oobIn) {
switch (byteCount) {
case 0u: currAddr = (uint16_t)inByte << 8; break;
case 1u: currAddr |= inByte; break;
default:
// Note: this is probably not modelling write behaviour properly.
logText("HAT ID writing byte 0x%0x to addr 0x%0x\n", inByte, currAddr);
mem[currAddr & (kMemSize - 1u)] = inByte;
// Auto-increment address; addressing wraps at the end of the page.
currAddr++;
if (!(currAddr & kPageSize)) currAddr -= kPageSize;
break;
}
byteCount++;
// Byte accepted, send ACK.
return true;
}

// Read a byte of data from the RPi Sense HAT ID EEPROM.
bool i2c_hat_id::readByte(uint32_t oobIn, uint8_t &outByte, uint32_t &oobOut) {
outByte = mem[currAddr & (kMemSize - 1u)];
oobOut = 0u;
logText("HAT ID reading byte 0x%0x from addr 0x%0x\n", outByte, currAddr);
// Auto-increment address.
currAddr++;
// Byte available, transmit to controller.
return true;
}
40 changes: 40 additions & 0 deletions dv/dpi/i2cdpi/i2c_hat_id.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#ifndef __DV_DPI_I2CDPI_I2C_HAT_ID_H_
#define __DV_DPI_I2CDPI_I2C_HAT_ID_H_
#include "i2cdevice.hh"

// Model of ID EEPROM on the Raspberry Pi Sense HAT.
class i2c_hat_id : public i2cdevice {
public:
i2c_hat_id(i2caddr_t addr);

// Physical properties of the EEPROM.
static constexpr unsigned kPageSize = 0x20u; // Pages are 32 bytes.
static constexpr unsigned kMemSize = 0x1000u; // 32Kib.
protected:
// Bus reset.
virtual void busReset();

// Start condition occurred on the I2C bus (broadcast to all devices).
virtual void startCond();

// Write a byte of data to the AS621x Digital Temperature Sensor.
virtual bool writeByte(uint8_t inByte, uint32_t oobIn);

// Read a byte of data from the AS621x Digital Temperature Sensor.
virtual bool readByte(uint32_t oobIn, uint8_t &outByte, uint32_t &oobOut);

private:
// Number of bytes transferred in current transcation.
uint8_t byteCount;

// Current address within the EEPROM.
uint16_t currAddr;

// Memory contents.
uint8_t mem[kMemSize];
};
#endif // __DV_DPI_I2CDPI_I2C_HAT_ID_H_
41 changes: 41 additions & 0 deletions dv/dpi/i2cdpi/i2c_lsm9ds1.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#include "i2c_lsm9ds1.hh"

// Device reset.
void i2c_lsm9ds1::busReset() {
i2cdevice::busReset();
regNum = 0u;
}

// Write a byte of data to the LSM9DS1 IMU.
bool i2c_lsm9ds1::writeByte(uint8_t inByte, uint32_t oobIn) {
// The model is very limited; we are interested only in the 'WHO_AM_I' ID registers for now.
regNum = inByte;
// Byte accepted, send ACK.
return true;
}

// Read a byte of data from LSM9DS1 IMU.
bool i2c_lsm9ds1::readByte(uint32_t oobIn, uint8_t &outByte, uint32_t &oobOut) {
// The model is very limited; we are interested only in the 'WHO_AM_I' ID registers for now.
switch (regNum) {
case 0x0fu: {
// This single IC presents as two I2C targets.
switch (devAddress) {
// Accelerometer and Gyroscope.
case 0x6au: outByte = 0x68u; break;
// Magnetic sensor.
case 0x1cu: outByte = 0x3du; break;
default: outByte = 0xffu; break;
}
}
break;
default: outByte = 0xffu; break;
}
oobOut = 0u;
// Byte available, transmit to controller.
return true;
}
32 changes: 32 additions & 0 deletions dv/dpi/i2cdpi/i2c_lsm9ds1.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#ifndef __DV_DPI_I2CDPI_I2C_LSM9DS1_H_
#define __DV_DPI_I2CDPI_I2C_LSM9DS1_H_
#include "i2cdevice.hh"

// iNEMO intertial module: 3D accelerometer, 3D gyroscope, 3D magnetometer.
// Included on the Raspberry Pi Sense HAT. Model just reponds to reads from 'WHO_AM_I' ID registers.
class i2c_lsm9ds1 : public i2cdevice {
public:
// Note: this IMU device is unusual in that it presents as two devices, at distinct addresses
// on the I2C bus. Therefore two device instances are required to model the full functionality.
i2c_lsm9ds1(i2caddr_t addr) : i2cdevice(addr) {
busReset();
}

protected:
// Bus reset.
virtual void busReset();

// Write a byte of data to the LSM9DS1 IMU.
virtual bool writeByte(uint8_t inByte, uint32_t oobIn);

// Read a byte of data from the LSM9DS1 IMU.
virtual bool readByte(uint32_t oobIn, uint8_t &outByte, uint32_t &oobOut);
private:
// Selected register.
uint8_t regNum;
};
#endif // __DV_DPI_I2CDPI_I2C_LSM9DS1_H_
18 changes: 18 additions & 0 deletions dv/dpi/i2cdpi/i2cdevice.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#include <stdarg.h>
#include <stdio.h>

#include "i2cdevice.hh"

// Logging utility function.
void i2cdevice::logText(const char *fmt, ...) {
if (logging) {
va_list va;
va_start(va, fmt);
vprintf(fmt, va);
va_end(va);
}
}
60 changes: 60 additions & 0 deletions dv/dpi/i2cdpi/i2cdevice.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#ifndef __DV_DPI_I2CDPI_I2CDEVICE_H_
#define __DV_DPI_I2CDPI_I2CDEVICE_H_
#include <stdint.h>

// I2C addresses are actually 7 bits typically.
typedef uint8_t i2caddr_t;

class i2cdevice {
public:
i2cdevice(i2caddr_t addr, bool log = false) : logging(log), devAddress(addr) {
busReset();
}
virtual ~i2cdevice() { }

// Return the I2C address of this device.
i2caddr_t getAddress() const { return devAddress; }

// Bus reset.
virtual void busReset(void) { }

// Start condition occurred on the I2C bus (broadcast to all devices).
virtual void startCond() { }

// Restart condition occurred on the I2C bus for this specific device.
virtual void restartCond() { }

// Access starting.
virtual void accessStarting(bool read) { }

// Stop condition occurred on the I2C bus for this specific device.
virtual void stopCond() { }

// Write a byte of data to the I2C device.
virtual bool writeByte(uint8_t inByte, uint32_t oobIn) {
// Sink consumes all write traffic.
return true;
}

// Read a byte of data from the I2C device.
virtual bool readByte(uint32_t oobIn, uint8_t &outByte, uint32_t &oobOut) {
// Sources no read traffic.
outByte = 0xffu; // Open drain bus.
oobOut = 0u;
return false;
}

protected:
// Diagnostic logging output.
void logText(const char *fmt, ...);

// Enable diagnostic logging output?
bool logging;

i2caddr_t devAddress;
};
#endif
Loading

0 comments on commit 80fcd12

Please sign in to comment.