diff --git a/include/spec/zynq/drivers/i2c.h b/include/spec/zynq/drivers/i2c.h new file mode 100644 index 0000000..d3b7fc1 --- /dev/null +++ b/include/spec/zynq/drivers/i2c.h @@ -0,0 +1,317 @@ +/* + * \brief Zynq I2C driver to be used with IO framework + * \author Johannes Schlatow + * \author Mark Albers + * \date 2015-03-12 + */ + +/* + * Copyright (C) 2015-2017 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _INCLUDE__DRIVERS__I2C_H_ +#define _INCLUDE__DRIVERS__I2C_H_ + +/* Genode includes */ +#include +#include +#include + +namespace Genode { + class I2c_driver; +} + +/** + * I2C driver for Zynq + */ +class Genode::I2c_driver : Genode::I2c_driver_base, Attached_io_mem_dataspace, Mmio +{ + private: + enum { + /* Maximal transfer size */ + I2C_MAX_TRANSFER_SIZE = 252, + + /* FIFO size */ + I2C_FIFO_DEPTH = 16, + + /* Number of bytes at data intr */ + I2C_DATA_INTR_DEPTH = 14, + }; + + int sendByteCount; + uint8_t *sendBufferPtr; + + /* + * Registers + */ + struct Control : Register<0x0, 16> + { + struct divisor_a : Bitfield<14,2> {}; + struct divisor_b : Bitfield<8,6> {}; + struct CLR_FIFO : Bitfield<6,1> {}; + struct SLVMON : Bitfield<5,1> {}; + struct HOLD : Bitfield<4,1> {}; + struct ACK_EN : Bitfield<3,1> {}; + struct NEA : Bitfield<2,1> {}; + struct MS : Bitfield<1,1> {}; + struct RW : Bitfield<0,1> { + enum { + SENDING = 0, + RECEIVING = 1, + }; + }; + }; + + struct Status : Register<0x4, 16> + { + struct BA : Bitfield<8,1> {}; + struct RXOVF : Bitfield<7,1> {}; + struct TXDV : Bitfield<6,1> {}; + struct RXDV : Bitfield<5,1> {}; + struct RXRW : Bitfield<3,1> {}; + }; + + struct I2C_address : Register<0x8, 16> + { + struct ADD : Bitfield<0,10> {}; + }; + + struct I2C_data : Register<0xC, 16> + { + struct DATA : Bitfield<0,8> {}; + }; + + template + struct Interrupt_register : Register + { + template + using Bitfield = typename Register::template Bitfield; + + struct ARB_LOST : Bitfield<9,1> {}; + struct RX_UNF : Bitfield<7,1> {}; + struct TX_OVF : Bitfield<6,1> {}; + struct RX_OVF : Bitfield<5,1> {}; + struct SLV_RDY : Bitfield<4,1> {}; + struct TO : Bitfield<3,1> {}; + struct NACK : Bitfield<2,1> {}; + struct DATA : Bitfield<1,1> {}; + struct COMP : Bitfield<0,1> {}; + }; + + struct Interrupt_status : Interrupt_register<0x10, 16> { }; + + struct Transfer_size : Register<0x14, 8> + { + struct SIZE : Bitfield<0,8> {}; + }; + + struct Slave_mon_pause : Register<0x18, 8> + { + struct PAUSE : Bitfield<0,4> {}; + }; + + struct Time_out : Register<0x1C, 8> + { + struct TO : Bitfield<0,8> {}; + }; + + struct Interrupt_mask : Interrupt_register<0x20, 16> { }; + + struct Interrupt_enable : Interrupt_register<0x24, 16> { }; + + struct Interrupt_disable : Interrupt_register<0x28, 16> { }; + + /* + * Helper functions + */ + void _init() + { + write(Control::divisor_a::bits(2) | + Control::divisor_b::bits(16) | + Control::ACK_EN::bits(1) | + Control::CLR_FIFO::bits(1) | + Control::NEA::bits(1) | + Control::MS::bits(1)); + } + + void _set_direction(int direction) + { + write(1); + write(direction); + } + + void _transmitFifoFill() + { + uint8_t availBytes; + int loopCnt; + int numBytesToSend; + + /* + * Determine number of bytes to write to FIFO. + */ + availBytes = I2C_FIFO_DEPTH - read(); + numBytesToSend = sendByteCount > availBytes ? availBytes : sendByteCount; + + /* + * Fill FIFO with amount determined above. + */ + for (loopCnt = 0; loopCnt < numBytesToSend; loopCnt++) + { + write(*sendBufferPtr); + sendBufferPtr++; + sendByteCount--; + } + } + + + public: + I2c_driver(Genode::Env &env, Genode::addr_t const mmio_base, Genode::size_t const mmio_size) : + Genode::Attached_io_mem_dataspace(env, mmio_base, mmio_size), + Genode::Mmio((Genode::addr_t)local_addr()) + { + _init(); + } + + int write_bytes(uint8_t slaveAddr, uint8_t *msgPtr, int byteCount, bool hold=false); + int read_bytes(uint8_t slaveAddr, uint8_t *msgPtr, int byteCount); +}; + +int Genode::I2c_driver::write_bytes(uint8_t slaveAddr, uint8_t *msgPtr, int byteCount, bool hold) +{ + uint32_t intrs, intrStatusReg; + + sendByteCount = byteCount; + sendBufferPtr = msgPtr; + + /* + * Set HOLD bit if byteCount is bigger than FIFO or HOLD is forced + */ + if (byteCount > I2C_FIFO_DEPTH || hold) write(1); + + /* + * Init sending master. + */ + _set_direction(Control::RW::SENDING); + + /* + * intrs keeps all the error-related interrupts. + */ + intrs = Interrupt_status::ARB_LOST::reg_mask() | + Interrupt_status::TX_OVF::reg_mask() | + Interrupt_status::NACK::reg_mask(); + + /* + * Clear the interrupt status register before use it to monitor. + */ + write(read()); + + /* + * Transmit first FIFO full of data. + */ + _transmitFifoFill(); + write(slaveAddr); + intrStatusReg = read(); + + /* + * Continue sending as long as there is more data and there are no errors. + */ + while ((sendByteCount > 0) && ((intrStatusReg & intrs) == 0)) + { + /* + * Wait until transmit FIFO is empty. + */ + if (read() != 0) + { + intrStatusReg = read(); + continue; + } + + /* + * Send more data out through transmit FIFO. + */ + _transmitFifoFill(); + } + + /* + * Check for completion of transfer. + */ + while ((intrStatusReg & Interrupt_status::COMP::reg_mask()) == 0) + { + + intrStatusReg = read(); + /* + * If there is an error, tell the caller. + */ + if ((intrStatusReg & intrs) != 0) + { + return 1; + } + } + + if (!hold) + write(0); + + return 0; +} + +int Genode::I2c_driver::read_bytes(uint8_t slaveAddr, uint8_t *msgPtr, int byteCount) +{ + uint32_t intrs, intrStatusReg; + + /* + * Init receiving master. + */ + _set_direction(Control::RW::RECEIVING); + + /* + * Clear the interrupt status register before use it to monitor. + */ + write(read()); + + /* + * Set up the transfer size register so the slave knows how much + * to send to us. + */ + + /* FIXME implement byteCount > 1 */ + if (byteCount > 1) + Genode::error("I2C driver: byteCount > 1 not implemented"); + + write(1); + + /* + * Set slave address. + */ + write(slaveAddr); + + /* + * intrs keeps all the error-related interrupts. + */ + intrs = Interrupt_status::ARB_LOST::reg_mask() | + Interrupt_status::RX_OVF::reg_mask() | + Interrupt_status::RX_UNF::reg_mask() | + Interrupt_status::NACK::reg_mask(); + + /* + * Poll the interrupt status register to find the errors. + */ + intrStatusReg = read(); + + while (read() != 1) + { + if (intrStatusReg & intrs) return 1; + } + + if (intrStatusReg & intrs) return 1; + + *msgPtr = read(); + + while (read() != 1) {} + + return 0; +} + +#endif /* _INCLUDE__DRIVERS__I2C_H_ */