| // 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); |