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