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