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/protocol/i2c.h>
10#include <ddk/protocol/platform-defs.h>
11#include <stdlib.h>
12#include <zircon/assert.h>
13#include <zircon/device/rtc.h>
14#include <librtc.h>
15
16typedef struct {
17    i2c_protocol_t i2c;
18} pcf8563_context;
19
20static zx_status_t set_utc_offset(const rtc_t* 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
26static ssize_t pcf8563_rtc_get(pcf8563_context *ctx, void* buf, size_t count) {
27    ZX_DEBUG_ASSERT(ctx);
28
29    rtc_t* rtc = buf;
30    if (count < sizeof *rtc) {
31        return ZX_ERR_BUFFER_TOO_SMALL;
32    }
33
34    uint8_t write_buf[] = { 0x02 };
35    uint8_t read_buf[7];
36    zx_status_t err = i2c_write_read_sync(&ctx->i2c, write_buf, sizeof write_buf, read_buf,
37                                          sizeof read_buf);
38    if (err) {
39        return err;
40    }
41
42    rtc->seconds = from_bcd(read_buf[0] & 0x7f);
43    rtc->minutes = from_bcd(read_buf[1] & 0x7f);
44    rtc->hours   = from_bcd(read_buf[2] & 0x3f);
45    rtc->day     = from_bcd(read_buf[3] & 0x3f);
46    rtc->month   = from_bcd(read_buf[5] & 0x1f);
47    rtc->year    = ((read_buf[5] & 0x80) ? 2000 : 1900) + from_bcd(read_buf[6]);
48
49    return sizeof *rtc;
50}
51
52static ssize_t pcf8563_rtc_set(pcf8563_context *ctx, const void* buf, size_t count) {
53    ZX_DEBUG_ASSERT(ctx);
54
55    const rtc_t* rtc = buf;
56    if (count < sizeof *rtc) {
57        return ZX_ERR_BUFFER_TOO_SMALL;
58    }
59
60    // An invalid time was supplied.
61    if (rtc_is_invalid(rtc)) {
62        return ZX_ERR_OUT_OF_RANGE;
63    }
64
65    int year = rtc->year;
66    int century = (year < 2000) ? 0 : 1;
67    if (century) {
68        year -= 2000;
69    } else {
70        year -= 1900;
71    }
72
73    uint8_t write_buf[] = {
74        0x02,
75        to_bcd(rtc->seconds),
76        to_bcd(rtc->minutes),
77        to_bcd(rtc->hours),
78        to_bcd(rtc->day),
79        0, // day of week
80        (century << 7) | to_bcd(rtc->month),
81        to_bcd(year)
82    };
83
84    zx_status_t err = i2c_write_read_sync(&ctx->i2c, write_buf, sizeof write_buf, NULL, 0);
85    if (err) {
86        return err;
87    }
88
89    zx_status_t status = set_utc_offset(rtc);
90    if (status != ZX_OK) {
91        zxlogf(ERROR, "The RTC driver was unable to set the UTC clock!\n");
92    }
93
94    return sizeof *rtc;
95}
96
97static zx_status_t pcf8563_rtc_ioctl(void* ctx, uint32_t op,
98                                     const void* in_buf, size_t in_len,
99                                     void* out_buf, size_t out_len, size_t* out_actual) {
100    switch (op) {
101    case IOCTL_RTC_GET: {
102        ssize_t ret = pcf8563_rtc_get(ctx, out_buf, out_len);
103        if (ret < 0) {
104            return ret;
105        }
106        *out_actual = ret;
107        return ZX_OK;
108    }
109    case IOCTL_RTC_SET:
110        return pcf8563_rtc_set(ctx, in_buf, in_len);
111    }
112    return ZX_ERR_NOT_SUPPORTED;
113}
114
115static zx_protocol_device_t pcf8563_rtc_device_proto = {
116    .version = DEVICE_OPS_VERSION,
117    .ioctl = pcf8563_rtc_ioctl,
118};
119
120static zx_status_t pcf8563_bind(void* ctx, zx_device_t* parent)
121{
122    pcf8563_context* context = calloc(1, sizeof *context);
123    if (!context) {
124        zxlogf(ERROR, "%s: failed to create device context\n", __FUNCTION__);
125        return ZX_ERR_NO_MEMORY;
126    }
127
128    zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_I2C, &context->i2c);
129    if (status != ZX_OK) {
130        zxlogf(ERROR, "%s: failed to acquire i2c\n", __FUNCTION__);
131        free(context);
132        return status;
133    }
134
135    device_add_args_t args = {
136        .version = DEVICE_ADD_ARGS_VERSION,
137        .name = "rtc",
138        .ops = &pcf8563_rtc_device_proto,
139        .proto_id = ZX_PROTOCOL_RTC,
140        .ctx = context
141    };
142
143    zx_device_t* dev;
144    status = device_add(parent, &args, &dev);
145    if (status != ZX_OK) {
146        free(context);
147        return status;
148    }
149
150    rtc_t rtc;
151    sanitize_rtc(context, &pcf8563_rtc_device_proto, &rtc);
152    status = set_utc_offset(&rtc);
153    if (status != ZX_OK) {
154        zxlogf(ERROR, "The RTC driver was unable to set the UTC clock!\n");
155    }
156
157    return ZX_OK;
158}
159
160static zx_driver_ops_t pcf8563_rtc_ops = {
161    .version = DRIVER_OPS_VERSION,
162    .bind = pcf8563_bind,
163};
164
165// clang-format off
166ZIRCON_DRIVER_BEGIN(pcf8563_rtc, pcf8563_rtc_ops, "pcf8563_rtc", "0.1", 3)
167    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_NXP),
168    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_PCF8563),
169    BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_PCF8563_RTC),
170ZIRCON_DRIVER_END(pcf8563_rtc)
171// clang-format on
172