1 // Copyright 2017 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/mmio-buffer.h>
10 #include <ddk/platform-defs.h>
11 #include <ddk/protocol/platform/device.h>
12 #include <ddk/protocol/platform-device-lib.h>
13
14 #include <zircon/syscalls.h>
15 #include <zircon/types.h>
16 #include <librtc.h>
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21
22 typedef struct pl031_regs {
23 volatile uint32_t dr;
24 volatile uint32_t mr;
25 volatile uint32_t lr;
26 volatile uint32_t cr;
27 volatile uint32_t msc;
28 volatile uint32_t ris;
29 volatile uint32_t mis;
30 volatile uint32_t icr;
31 } pl031_regs_t;
32
33 typedef struct pl031 {
34 zx_device_t* parent;
35 mmio_buffer_t mmio;
36 pl031_regs_t* regs;
37 } pl031_t;
38
set_utc_offset(const fuchsia_hardware_rtc_Time * rtc)39 static zx_status_t set_utc_offset(const fuchsia_hardware_rtc_Time* rtc) {
40 uint64_t rtc_nanoseconds = seconds_since_epoch(rtc) * 1000000000;
41 int64_t offset = rtc_nanoseconds - zx_clock_get_monotonic();
42 return zx_clock_adjust(get_root_resource(), ZX_CLOCK_UTC, offset);
43 }
44
pl031_rtc_get(void * ctx,fuchsia_hardware_rtc_Time * rtc)45 static zx_status_t pl031_rtc_get(void* ctx, fuchsia_hardware_rtc_Time* rtc) {
46 ZX_DEBUG_ASSERT(ctx);
47 pl031_t *context = ctx;
48 seconds_to_rtc(context->regs->dr, rtc);
49 return ZX_OK;
50 }
51
pl031_rtc_set(void * ctx,const fuchsia_hardware_rtc_Time * rtc)52 static zx_status_t pl031_rtc_set(void* ctx, const fuchsia_hardware_rtc_Time* rtc) {
53 ZX_DEBUG_ASSERT(ctx);
54
55 // An invalid time was supplied.
56 if (rtc_is_invalid(rtc)) {
57 return ZX_ERR_OUT_OF_RANGE;
58 }
59
60 pl031_t *context = ctx;
61 context->regs->lr = seconds_since_epoch(rtc);
62
63 zx_status_t status = set_utc_offset(rtc);
64 if (status != ZX_OK) {
65 zxlogf(ERROR, "The RTC driver was unable to set the UTC clock!\n");
66 }
67
68 return ZX_OK;
69 }
70
fidl_Get(void * ctx,fidl_txn_t * txn)71 static zx_status_t fidl_Get(void* ctx, fidl_txn_t* txn) {
72 fuchsia_hardware_rtc_Time rtc;
73 pl031_rtc_get(ctx, &rtc);
74 return fuchsia_hardware_rtc_DeviceGet_reply(txn, &rtc);
75 }
76
fidl_Set(void * ctx,const fuchsia_hardware_rtc_Time * rtc,fidl_txn_t * txn)77 static zx_status_t fidl_Set(void* ctx, const fuchsia_hardware_rtc_Time* rtc, fidl_txn_t* txn) {
78 zx_status_t status = pl031_rtc_set(ctx, rtc);
79 return fuchsia_hardware_rtc_DeviceSet_reply(txn, status);
80 }
81
82 static fuchsia_hardware_rtc_Device_ops_t fidl_ops = {
83 .Get = fidl_Get,
84 .Set = fidl_Set,
85 };
86
pl031_rtc_message(void * ctx,fidl_msg_t * msg,fidl_txn_t * txn)87 static zx_status_t pl031_rtc_message(void* ctx, fidl_msg_t* msg, fidl_txn_t* txn) {
88 return fuchsia_hardware_rtc_Device_dispatch(ctx, txn, msg, &fidl_ops);
89 }
90
91 static zx_protocol_device_t pl031_rtc_device_proto = {
92 .version = DEVICE_OPS_VERSION,
93 .message = pl031_rtc_message
94 };
95
pl031_rtc_bind(void * ctx,zx_device_t * parent)96 static zx_status_t pl031_rtc_bind(void* ctx, zx_device_t* parent) {
97 zxlogf(TRACE, "pl031_rtc: bind parent = %p\n", parent);
98
99 pdev_protocol_t proto;
100 zx_status_t st = device_get_protocol(parent, ZX_PROTOCOL_PDEV, &proto);
101 if (st != ZX_OK) {
102 return st;
103 }
104
105 // Allocate a new device object for the bus.
106 pl031_t* pl031 = calloc(1, sizeof(*pl031));
107 if (!pl031) {
108 zxlogf(ERROR, "pl031_rtc: bind failed to allocate pl031_t struct\n");
109 return ZX_ERR_NO_MEMORY;
110 }
111
112 // Carve out some address space for this device.
113 st = pdev_map_mmio_buffer2(&proto, 0, ZX_CACHE_POLICY_UNCACHED_DEVICE, &pl031->mmio);
114 if (st != ZX_OK) {
115 zxlogf(ERROR, "pl031_rtc: bind failed to pdev_map_mmio.\n");
116 goto error_return;
117 }
118 pl031->regs = pl031->mmio.vaddr;
119
120 pl031->parent = parent;
121
122 // bind the device
123 device_add_args_t args = {
124 .version = DEVICE_ADD_ARGS_VERSION,
125 .name = "rtc",
126 .proto_id = ZX_PROTOCOL_RTC,
127 .ops = &pl031_rtc_device_proto,
128 .ctx = pl031
129 };
130
131 zx_device_t* dev;
132 st = device_add(parent, &args, &dev);
133 if (st != ZX_OK) {
134 zxlogf(ERROR, "pl031_rtc: error adding device\n");
135 goto error_return;
136 }
137
138 // set the current RTC offset in the kernel
139 fuchsia_hardware_rtc_Time rtc;
140 sanitize_rtc(pl031, &rtc, pl031_rtc_get, pl031_rtc_set);
141 st = set_utc_offset(&rtc);
142 if (st != ZX_OK) {
143 zxlogf(ERROR, "pl031_rtc: unable to set the UTC clock!\n");
144 }
145
146 return ZX_OK;
147
148 error_return:
149 if (pl031) {
150 mmio_buffer_release(&pl031->mmio);
151 free(pl031);
152 }
153
154 return st;
155 }
156
pl031_rtc_release(void * ctx)157 static void pl031_rtc_release(void* ctx) {
158 pl031_t* pl031 = (pl031_t*)ctx;
159 mmio_buffer_release(&pl031->mmio);
160 free(pl031);
161 }
162
163 static zx_driver_ops_t pl031_rtc_driver_ops = {
164 .version = DRIVER_OPS_VERSION,
165 .bind = pl031_rtc_bind,
166 .release = pl031_rtc_release,
167 };
168
169 // The formatter does not play nice with these macros.
170 // clang-format off
171 ZIRCON_DRIVER_BEGIN(pl031, pl031_rtc_driver_ops, "zircon", "0.1", 3)
172 BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_GENERIC),
173 BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_GENERIC),
174 BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_RTC_PL031),
175 ZIRCON_DRIVER_END(pl031)
176 // clang-format on
177