blob: 7083a264b4326c01c42791ef84e55e80fac97109 [file] [log] [blame]
// Copyright 2023 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 "pcf8563.h"
#include <fidl/fuchsia.hardware.i2c/cpp/fidl.h>
#include <fidl/fuchsia.hardware.rtc/cpp/fidl.h>
#include <lib/driver/component/cpp/driver_export.h>
#include <lib/driver/logging/cpp/logger.h>
#include <lib/fit/function.h>
#include <lib/zx/result.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "pcf8563-server.h"
namespace fdf {
using namespace fuchsia_driver_framework;
} // namespace fdf
namespace pcf8563 {
namespace {
namespace fi2c = fuchsia_hardware_i2c;
namespace frtc = fuchsia_hardware_rtc;
uint8_t to_bcd(uint8_t binary) {
return static_cast<uint8_t>((((binary / 10) << 4) | (binary % 10)));
}
uint8_t from_bcd(uint8_t bcd) { return ((bcd >> 4) * 10) + (bcd & 0xf); }
} // namespace
zx::result<> RtcDriver::Start() {
{
auto result = incoming()->Connect<fi2c::Service::Device>();
if (result.is_error()) {
FDF_LOG(ERROR, "Connect(): %s", result.status_string());
return result.take_error();
}
i2c_.Bind(std::move(result.value()));
}
server_ = std::make_unique<RtcServer>(this);
{
auto result = outgoing()->AddService<frtc::Service>(server_->GetInstanceHandler());
if (result.is_error()) {
FDF_LOG(ERROR, "AddService(): %s", result.status_string());
return result.take_error();
}
}
{
auto result = CreateDevfsNode();
if (result.is_error()) {
FDF_LOG(ERROR, "CreateDevfsNode(): %s", result.status_string());
return result.take_error();
}
}
return zx::ok();
}
zx::result<frtc::Time> RtcDriver::Read() {
std::vector<fi2c::Transaction> txns{
{{.data_transfer = fi2c::DataTransfer::WithWriteData({0x02})}},
{{.data_transfer = fi2c::DataTransfer::WithReadSize(7)}},
};
auto result = i2c_->Transfer({{.transactions = std::move(txns)}});
if (result.is_error()) {
FDF_LOG(ERROR, "i2c_.Transfer(): %s", result.error_value().FormatDescription().c_str());
if (result.error_value().is_framework_error()) {
return zx::error(ZX_ERR_INTERNAL);
}
return zx::error(result.error_value().domain_error());
}
// Transfer() returns a vector of byte-vectors, one per read-transfer.
std::vector<uint8_t> rx_data = result->read_data()[0];
frtc::Time time{{
.seconds = from_bcd(rx_data[0] & 0x7f),
.minutes = from_bcd(rx_data[1] & 0x7f),
.hours = from_bcd(rx_data[2] & 0x3f),
.day = from_bcd(rx_data[3] & 0x3f),
// .weekday unused.
.month = from_bcd(rx_data[5] & 0x1f),
.year = static_cast<uint16_t>(((rx_data[5] & 0x80) ? 2000 : 1900) + from_bcd(rx_data[6])),
}};
return zx::ok(time);
}
zx::result<> RtcDriver::Write(frtc::Time rtc) {
int year = rtc.year();
// The PCF8563 uses 1 BCD-encoded byte for the year (0-99). The century is encoded as the high-bit
// of the month field whose value represents century_bit ? 2000 : 1900. As a consequence, this
// chip can only support dates whose year is in the range 1900 through 2099.
if (year < 1900 || year > 2099) {
FDF_LOG(ERROR, "year=%d, must be between 1900 and 2099", year);
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
uint8_t century_bit = (year < 2000) ? 0 : 1;
year -= century_bit ? 2000 : 1900; // Normalize to a value between 0 and 99.
std::vector<uint8_t> tx_data = {
0x02,
to_bcd(rtc.seconds()),
to_bcd(rtc.minutes()),
to_bcd(rtc.hours()),
to_bcd(rtc.day()),
0, // day of week
static_cast<uint8_t>(century_bit << 7 | to_bcd(rtc.month())),
to_bcd(static_cast<uint8_t>(year)),
};
std::vector<fi2c::Transaction> txns{
{{.data_transfer = fi2c::DataTransfer::WithWriteData(std::move(tx_data))}},
};
auto result = i2c_->Transfer({{.transactions = std::move(txns)}});
if (result.is_error()) {
FDF_LOG(ERROR, "i2c_.Transfer(): %s", result.error_value().FormatDescription().c_str());
if (result.error_value().is_framework_error()) {
return zx::error(ZX_ERR_INTERNAL);
}
return zx::error(result.error_value().domain_error());
}
return zx::ok();
}
void RtcDriver::DevfsConnect(fidl::ServerEnd<frtc::Device> req) {
server_->bindings().AddBinding(dispatcher(), std::move(req), server_.get(),
fidl::kIgnoreBindingClosure);
}
zx::result<> RtcDriver::CreateDevfsNode() {
auto connector = devfs_connector_.Bind(dispatcher());
if (connector.is_error()) {
FDF_LOG(ERROR, "devfs_connector_.Bind(): %s", connector.status_string());
return connector.take_error();
}
fdf::DevfsAddArgs devfs;
devfs.connector(std::move(connector.value()));
devfs.class_name("rtc");
fdf::NodeAddArgs args;
args.devfs_args(std::move(devfs));
args.name("rtc");
// server-end required by AddChild().
auto controller_eps = fidl::CreateEndpoints<fdf::NodeController>();
if (controller_eps.is_error()) {
FDF_LOG(ERROR, "fidl::CreateEndpoints<NodeController>(): %s", controller_eps.status_string());
return controller_eps.take_error();
}
node_controller_.Bind(std::move(controller_eps->client));
// server-end required by AddChild().
auto node_eps = fidl::CreateEndpoints<fdf::Node>();
if (node_eps.is_error()) {
FDF_LOG(ERROR, "fidl::CreateEndpoints<Node>(): %s", node_eps.status_string());
return node_eps.take_error();
}
node_.Bind(std::move(node_eps->client));
auto result = fidl::Call(node())->AddChild({{
.args = std::move(args),
.controller = std::move(controller_eps->server),
.node = std::move(node_eps->server),
}});
if (result.is_error()) {
FDF_LOG(ERROR, "AddChild(): %s", result.error_value().FormatDescription().c_str());
// Node API assumes bespoke error and does not use zx_status_t.
return zx::error(ZX_ERR_INTERNAL);
}
return zx::ok();
}
} // namespace pcf8563
FUCHSIA_DRIVER_EXPORT(pcf8563::RtcDriver);