blob: db1c87686058a798d6a621cfeb328083203e5062 [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.
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/platform-defs.h>
#include <ddk/protocol/i2c.h>
#include <ddktl/device.h>
#include <ddktl/protocol/dotmatrixdisplay.h>
#include <ddktl/protocol/i2c.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_call.h>
#include <fuchsia/hardware/dotmatrixdisplay/c/fidl.h>
#include <lib/device-protocol/i2c-channel.h>
#include <lib/device-protocol/i2c.h>
#include <threads.h>
#include <lib/zircon-internal/thread_annotations.h>
#include <array>
class Ssd1306;
using DeviceType = ddk::Device<Ssd1306, ddk::UnbindableNew, ddk::Messageable>;
class Ssd1306 : public DeviceType,
public ddk::DotmatrixDisplayProtocol<Ssd1306, ddk::base_protocol> {
public:
Ssd1306(zx_device_t* parent) : DeviceType(parent), frame_buffer_(), i2c_(parent) {}
zx_status_t Bind();
zx_status_t DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn);
void DdkUnbindNew(ddk::UnbindTxn txn);
void DdkRelease() { delete this; }
void DotmatrixDisplayGetConfig(dotmatrix_display_config_t* out_config);
zx_status_t DotmatrixDisplaySetScreen(const uint8_t* screen_buffer_list,
size_t screen_buffer_count);
static zx_status_t FidlGetConfig(void* ctx, fidl_txn_t* txn);
static zx_status_t FidlSetScreen(void* ctx, const uint8_t* screen_buffer_list,
size_t screen_buffer_count, fidl_txn* txn);
zx_status_t FlushScreen();
private:
static constexpr int kDefaultColor = 0xFF;
static constexpr int kDisplayWidth = 128;
static_assert((kDisplayWidth % 8 == 0));
static constexpr int kDisplayHeight = 64;
static_assert((kDisplayHeight % 8 == 0));
static constexpr int kI2cFbAddress = 0x40;
static constexpr uint8_t kPowerOnSequence[] = {
0xAE, 0x00, 0x10, 0x40, 0xB0, 0x81, 0xCF, 0xA1, 0xA6, 0xA8, 0x3F, 0xC8, 0xD3,
0x00, 0xD5, 0x80, 0xD9, 0xF1, 0xDA, 0x12, 0xDB, 0x40, 0x8D, 0x14, 0xAF, 0xAF,
};
zx_status_t EnableScreen();
thrd_t enable_thread_;
bool is_enabled_ = false;
std::array<std::array<uint8_t, kDisplayWidth>, kDisplayHeight / 8> frame_buffer_;
ddk::I2cChannel i2c_;
};
zx_status_t Ssd1306::FidlGetConfig(void* ctx, fidl_txn_t* txn) {
fuchsia_hardware_dotmatrixdisplay_DotmatrixDisplayConfig config = {};
config.width = kDisplayWidth;
config.height = kDisplayHeight;
config.format = fuchsia_hardware_dotmatrixdisplay_PixelFormat_MONOCHROME;
config.layout = fuchsia_hardware_dotmatrixdisplay_ScreenLayout_COLUMN_TB_ROW_LR;
return fuchsia_hardware_dotmatrixdisplay_DotmatrixDisplayGetConfig_reply(txn, &config);
}
zx_status_t Ssd1306::FidlSetScreen(void* ctx, const uint8_t* screen_buffer_list,
size_t screen_buffer_count, fidl_txn* txn) {
Ssd1306* ssd = reinterpret_cast<Ssd1306*>(ctx);
zx_status_t status = ssd->DotmatrixDisplaySetScreen(screen_buffer_list, screen_buffer_count);
return fuchsia_hardware_dotmatrixdisplay_DotmatrixDisplaySetScreen_reply(txn, status);
}
zx_status_t Ssd1306::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
static const fuchsia_hardware_dotmatrixdisplay_DotmatrixDisplay_ops_t kOps = {
.GetConfig = Ssd1306::FidlGetConfig,
.SetScreen = Ssd1306::FidlSetScreen,
};
return fuchsia_hardware_dotmatrixdisplay_DotmatrixDisplay_dispatch(this, txn, msg, &kOps);
}
static zx_status_t WriteCommand(ddk::I2cChannel& i2c, uint8_t reg_address, uint8_t data) {
uint8_t send[2] = {reg_address, data};
return i2c.WriteSync(send, static_cast<uint8_t>(sizeof(send)));
}
void Ssd1306::DotmatrixDisplayGetConfig(dotmatrix_display_config_t* out_config) {
out_config->width = kDisplayWidth;
out_config->height = kDisplayHeight;
out_config->format = PIXEL_FORMAT_MONOCHROME;
out_config->layout = SCREEN_LAYOUT_COLUMN_TB_ROW_LR;
}
zx_status_t Ssd1306::DotmatrixDisplaySetScreen(const uint8_t* screen_buffer_list,
size_t screen_buffer_count) {
if (screen_buffer_count > (kDisplayWidth * kDisplayHeight / 8)) {
return ZX_ERR_OUT_OF_RANGE;
}
size_t row = 0;
size_t col = 0;
for (size_t i = 0; i < screen_buffer_count; i++) {
frame_buffer_[row][col] = screen_buffer_list[i];
col++;
if (col == kDisplayWidth) {
col = 0;
row++;
}
}
return FlushScreen();
}
zx_status_t Ssd1306::FlushScreen() {
if (!is_enabled_) {
return ZX_ERR_SHOULD_WAIT;
}
for (int i = 0; i < (kDisplayHeight / 8); i++) {
zx_status_t status = WriteCommand(i2c_, 0x00, static_cast<uint8_t>(0xB0 + i));
if (status != ZX_OK) {
return status;
}
status = WriteCommand(i2c_, 0x00, 0x00);
if (status != ZX_OK) {
return status;
}
status = WriteCommand(i2c_, 0x00, 0x10);
if (status != ZX_OK) {
return status;
}
uint8_t v[kDisplayWidth + 1];
v[0] = kI2cFbAddress;
for (size_t j = 1; j < countof(v); j++) {
v[j] = frame_buffer_[i][j - 1];
}
status = i2c_.WriteSync(v, sizeof(v));
if (status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
zx_status_t Ssd1306::EnableScreen() {
zx_status_t status;
for (size_t i = 0; i < countof(kPowerOnSequence); i++) {
status = WriteCommand(i2c_, 0x00, kPowerOnSequence[i]);
if (status != ZX_OK) {
return status;
}
}
// Set screen to solid on-color.
for (int i = 0; i < (kDisplayHeight / 8); i++) {
for (size_t j = 0; j < kDisplayWidth; j++) {
frame_buffer_[i][j] = kDefaultColor;
}
}
is_enabled_ = true;
status = FlushScreen();
if (status != ZX_OK) {
is_enabled_ = false;
return status;
}
return ZX_OK;
}
void Ssd1306::DdkUnbindNew(ddk::UnbindTxn txn) {
thrd_join(enable_thread_, NULL);
txn.Reply();
}
zx_status_t Ssd1306::Bind() {
auto f = [](void* arg) -> int {
zx_status_t status = reinterpret_cast<Ssd1306*>(arg)->EnableScreen();
if (status != ZX_OK) {
return status;
}
return 0;
};
int rc = thrd_create_with_name(&enable_thread_, f, this, "ssd1306-thread");
if (rc != thrd_success) {
return ZX_ERR_INTERNAL;
}
return DdkAdd("ssd1306");
}
zx_status_t ssd1306_bind(void* ctx, zx_device_t* device) {
fbl::AllocChecker ac;
auto dev = fbl::make_unique_checked<Ssd1306>(&ac, device);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
auto status = dev->Bind();
if (status == ZX_OK) {
// devmgr is now in charge of the memory for dev
__UNUSED auto ptr = dev.release();
}
return status;
}
static zx_driver_ops_t ssd1306_driver_ops = []() -> zx_driver_ops_t {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = ssd1306_bind;
return ops;
}();
// clang-format off
ZIRCON_DRIVER_BEGIN(ssd1306, ssd1306_driver_ops, "zircon", "0.1", 3)
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_GENERIC),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_GENERIC),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_SSD1306),
ZIRCON_DRIVER_END(ssd1306)
// clang-format on