1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <ddk/binding.h>
6 #include <ddk/debug.h>
7 #include <ddk/device.h>
8 #include <ddk/driver.h>
9 #include <ddk/platform-defs.h>
10 #include <ddk/protocol/i2c.h>
11 #include <ddk/protocol/i2c-lib.h>
12 #include <stdlib.h>
13 #include <zircon/assert.h>
14 #include <librtc.h>
15
16 typedef struct {
17 i2c_protocol_t i2c;
18 } pcf8563_context;
19
set_utc_offset(const fuchsia_hardware_rtc_Time * rtc)20 static zx_status_t set_utc_offset(const fuchsia_hardware_rtc_Time* rtc) {
21 uint64_t rtc_nanoseconds = seconds_since_epoch(rtc) * 1000000000;;
22 int64_t offset = rtc_nanoseconds - zx_clock_get_monotonic();
23 return zx_clock_adjust(get_root_resource(), ZX_CLOCK_UTC, offset);
24 }
25
pcf8563_rtc_get(void * ctx,fuchsia_hardware_rtc_Time * rtc)26 static zx_status_t pcf8563_rtc_get(void* ctx, fuchsia_hardware_rtc_Time* rtc) {
27 ZX_DEBUG_ASSERT(ctx);
28
29 pcf8563_context *context = ctx;
30 uint8_t write_buf[] = { 0x02 };
31 uint8_t read_buf[7];
32 zx_status_t err = i2c_write_read_sync(&context->i2c, write_buf, sizeof write_buf, read_buf,
33 sizeof read_buf);
34 if (err) {
35 return err;
36 }
37
38 rtc->seconds = from_bcd(read_buf[0] & 0x7f);
39 rtc->minutes = from_bcd(read_buf[1] & 0x7f);
40 rtc->hours = from_bcd(read_buf[2] & 0x3f);
41 rtc->day = from_bcd(read_buf[3] & 0x3f);
42 rtc->month = from_bcd(read_buf[5] & 0x1f);
43 rtc->year = ((read_buf[5] & 0x80) ? 2000 : 1900) + from_bcd(read_buf[6]);
44
45 return ZX_OK;
46 }
47
pcf8563_rtc_set(void * ctx,const fuchsia_hardware_rtc_Time * rtc)48 static zx_status_t pcf8563_rtc_set(void* ctx, const fuchsia_hardware_rtc_Time* rtc) {
49 ZX_DEBUG_ASSERT(ctx);
50
51 // An invalid time was supplied.
52 if (rtc_is_invalid(rtc)) {
53 return ZX_ERR_OUT_OF_RANGE;
54 }
55
56 int year = rtc->year;
57 int century = (year < 2000) ? 0 : 1;
58 if (century) {
59 year -= 2000;
60 } else {
61 year -= 1900;
62 }
63
64 uint8_t write_buf[] = {
65 0x02,
66 to_bcd(rtc->seconds),
67 to_bcd(rtc->minutes),
68 to_bcd(rtc->hours),
69 to_bcd(rtc->day),
70 0, // day of week
71 (century << 7) | to_bcd(rtc->month),
72 to_bcd(year)
73 };
74
75 pcf8563_context *context = ctx;
76 zx_status_t err = i2c_write_read_sync(&context->i2c, write_buf, sizeof write_buf, NULL, 0);
77 if (err) {
78 return err;
79 }
80
81 zx_status_t status = set_utc_offset(rtc);
82 if (status != ZX_OK) {
83 zxlogf(ERROR, "The RTC driver was unable to set the UTC clock!\n");
84 }
85
86 return ZX_OK;
87 }
88
fidl_Get(void * ctx,fidl_txn_t * txn)89 static zx_status_t fidl_Get(void* ctx, fidl_txn_t* txn) {
90 fuchsia_hardware_rtc_Time rtc;
91 pcf8563_rtc_get(ctx, &rtc);
92 return fuchsia_hardware_rtc_DeviceGet_reply(txn, &rtc);
93 }
94
fidl_Set(void * ctx,const fuchsia_hardware_rtc_Time * rtc,fidl_txn_t * txn)95 static zx_status_t fidl_Set(void* ctx, const fuchsia_hardware_rtc_Time* rtc, fidl_txn_t* txn) {
96 zx_status_t status = pcf8563_rtc_set(ctx, rtc);
97 return fuchsia_hardware_rtc_DeviceSet_reply(txn, status);
98 }
99
100 static fuchsia_hardware_rtc_Device_ops_t fidl_ops = {
101 .Get = fidl_Get,
102 .Set = fidl_Set,
103 };
104
pcf8563_rtc_message(void * ctx,fidl_msg_t * msg,fidl_txn_t * txn)105 static zx_status_t pcf8563_rtc_message(void* ctx, fidl_msg_t* msg, fidl_txn_t* txn) {
106 return fuchsia_hardware_rtc_Device_dispatch(ctx, txn, msg, &fidl_ops);
107 }
108
109 static zx_protocol_device_t pcf8563_rtc_device_proto = {
110 .version = DEVICE_OPS_VERSION,
111 .message = pcf8563_rtc_message
112 };
113
pcf8563_bind(void * ctx,zx_device_t * parent)114 static zx_status_t pcf8563_bind(void* ctx, zx_device_t* parent)
115 {
116 pcf8563_context* context = calloc(1, sizeof *context);
117 if (!context) {
118 zxlogf(ERROR, "%s: failed to create device context\n", __FUNCTION__);
119 return ZX_ERR_NO_MEMORY;
120 }
121
122 zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_I2C, &context->i2c);
123 if (status != ZX_OK) {
124 zxlogf(ERROR, "%s: failed to acquire i2c\n", __FUNCTION__);
125 free(context);
126 return status;
127 }
128
129 device_add_args_t args = {
130 .version = DEVICE_ADD_ARGS_VERSION,
131 .name = "rtc",
132 .ops = &pcf8563_rtc_device_proto,
133 .proto_id = ZX_PROTOCOL_RTC,
134 .ctx = context
135 };
136
137 zx_device_t* dev;
138 status = device_add(parent, &args, &dev);
139 if (status != ZX_OK) {
140 free(context);
141 return status;
142 }
143
144 fuchsia_hardware_rtc_Time rtc;
145 sanitize_rtc(context, &rtc, pcf8563_rtc_get, pcf8563_rtc_set);
146 status = set_utc_offset(&rtc);
147 if (status != ZX_OK) {
148 zxlogf(ERROR, "The RTC driver was unable to set the UTC clock!\n");
149 }
150
151 return ZX_OK;
152 }
153
154 static zx_driver_ops_t pcf8563_rtc_ops = {
155 .version = DRIVER_OPS_VERSION,
156 .bind = pcf8563_bind,
157 };
158
159 // clang-format off
160 ZIRCON_DRIVER_BEGIN(pcf8563_rtc, pcf8563_rtc_ops, "pcf8563_rtc", "0.1", 2)
161 BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_NXP),
162 BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_PCF8563_RTC),
163 ZIRCON_DRIVER_END(pcf8563_rtc)
164 // clang-format on
165