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