blob: 69f027854856f7ea7f709f8a23cc6933064a7588 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ZIRCON_SYSTEM_DEV_CODEC_ALC5663_FAKE_I2C_H_
#define ZIRCON_SYSTEM_DEV_CODEC_ALC5663_FAKE_I2C_H_
#include <ddk/debug.h>
#include <ddk/driver.h>
#include <ddk/protocol/i2c.h>
#include <ddktl/device.h>
#include <ddktl/protocol/empty-protocol.h>
#include <ddktl/protocol/i2c.h>
#include <fbl/string_buffer.h>
#include <fbl/string_printf.h>
#include <fbl/vector.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/fit/function.h>
namespace audio::alc5663 {
// A fake I2C device.
//
// This class helps users implement fake I2C hardware. In particular,
// hardware which uses the style of having reads and writes of
// fixed-size data words at fixed-size addresses can use this class to
// listen and respond to the reads and writes of an I2C driver.
//
// The constructor takes two callbacks: "on_read" and "on_write" which
// will be invoked each time the driver performs a read or write
// respectively.
template <typename AddressType, typename DataType>
class FakeI2c : ddk::I2cProtocol<FakeI2c<AddressType, DataType>> {
public:
// Construct a FakeI2c, which will call the given function "on_read" each time
// a read takes place to this device, and similarly call "on_write" for writes.
FakeI2c(fit::function<DataType(AddressType)> on_read,
fit::function<void(AddressType, DataType)> on_write)
: ddk::I2cProtocol<FakeI2c>(), on_read_(std::move(on_read)), on_write_(std::move(on_write)) {}
// Get the protocol ops for this object.
i2c_protocol_t GetProto() { return {&this->i2c_protocol_ops_, this}; }
// Implementation of |ddk::I2cProtocol|.
void I2cTransact(const i2c_op_t* op_list, size_t op_count, i2c_transact_callback callback,
void* cookie);
zx_status_t I2cGetMaxTransferSize(size_t* /*out_size*/) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t I2cGetInterrupt(uint32_t /*flags*/, zx::interrupt* /*out_irq*/) {
return ZX_ERR_NOT_SUPPORTED;
}
private:
fit::function<DataType(AddressType)> on_read_;
fit::function<void(AddressType, DataType)> on_write_;
};
// Implementation details follow.
template <typename AddressType, typename DataType>
void FakeI2c<AddressType, DataType>::I2cTransact(const i2c_op_t* op_list, size_t op_count,
i2c_transact_callback callback, void* cookie) {
// Is this a read? (represented on the wire as a write of an address followed by a read.)
if (op_count == 2 && !op_list[0].is_read && op_list[0].data_size == sizeof(AddressType) &&
op_list[1].is_read && op_list[1].data_size == sizeof(DataType)) {
// Decode address.
AddressType addr = (reinterpret_cast<const AddressType*>(op_list[0].data_buffer))[0];
// Issue callback.
DataType result = on_read_(addr);
// Return to caller.
fbl::Vector<i2c_op_t> result_ops = {{
.data_buffer = &result,
.data_size = sizeof(result),
.is_read = false,
.stop = true,
}};
callback(cookie, ZX_OK, result_ops.get(), result_ops.size());
return;
}
// Is this a write? (represented on the wire as a write of an address, followed by a write of
// data.)
if (op_count == 1 && !op_list[0].is_read && op_list[0].stop &&
op_list[0].data_size == sizeof(DataType) + sizeof(AddressType)) {
// Decode data.
struct Payload {
AddressType address;
DataType data;
} __PACKED;
const auto* payload = reinterpret_cast<const Payload*>(op_list[0].data_buffer);
// Issue callback.
on_write_(payload->address, payload->data);
// Return to caller.
fbl::Vector<i2c_op_t> empty_result = {};
callback(cookie, ZX_OK, empty_result.get(), empty_result.size());
return;
}
// Can't handle this: log the transaction, and abort execution.
zxlogf(ERROR, "Unsupported I2C transation:\n");
for (size_t i = 0; i < op_count; i++) {
if (op_list[i].is_read) {
zxlogf(ERROR, " * READ of %ld byte(s)\n", op_list[i].data_size);
} else {
fbl::StringBuffer<1024> buff;
const auto* data = reinterpret_cast<const uint8_t*>(op_list[i].data_buffer);
for (size_t j = 0; j < op_list[i].data_size; j++) {
buff.Append(fbl::StringPrintf(" %02x", data[j]));
}
zxlogf(ERROR, " * WRITE of %ld byte(s): %s\n", op_list[i].data_size, buff.c_str());
}
if (op_list[i].stop) {
zxlogf(ERROR, " * STOP\n");
}
}
ZX_PANIC("Unsupported I2C transaction.");
}
} // namespace audio::alc5663
#endif // ZIRCON_SYSTEM_DEV_CODEC_ALC5663_FAKE_I2C_H_