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/protocol/platform-defs.h>
10#include <ddk/protocol/platform-device.h>
11#include <zircon/device/rtc.h>
12
13#include <zircon/syscalls.h>
14#include <zircon/types.h>
15
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19
20typedef struct pl031_regs {
21    volatile uint32_t dr;
22    volatile uint32_t mr;
23    volatile uint32_t lr;
24    volatile uint32_t cr;
25    volatile uint32_t msc;
26    volatile uint32_t ris;
27    volatile uint32_t mis;
28    volatile uint32_t icr;
29} pl031_regs_t;
30
31typedef struct pl031 {
32    zx_device_t* parent;
33
34    pl031_regs_t* regs;
35} pl031_t;
36
37static zx_protocol_device_t pl031_rtc_device_proto = {
38    .version = DEVICE_OPS_VERSION,
39};
40
41static void pl031_set_kernel_offset(pl031_t* pl031) {
42    // the data register seems to hold second offsets
43    uint32_t offset32;
44    uint32_t last = 0;
45
46    // read the value until it rolls, then latch that
47    last = pl031->regs->dr;
48    do {
49        offset32 = pl031->regs->dr;
50    } while (offset32 != 0 && offset32 == last);
51
52    if (offset32 == 0) {
53        zxlogf(ERROR, "pl031_rtc: zero read from DR, aborting\n");
54        return;
55    }
56
57    int64_t offset = ZX_SEC(offset32);
58    zx_status_t status = zx_clock_adjust(get_root_resource(), ZX_CLOCK_UTC, offset);
59    if (status != ZX_OK) {
60        zxlogf(ERROR, "The RTC driver was unable to set the UTC clock!\n");
61    }
62}
63
64static zx_status_t pl031_rtc_bind(void* ctx, zx_device_t* parent) {
65    zxlogf(TRACE, "pl031_rtc: bind parent = %p\n", parent);
66
67    platform_device_protocol_t proto;
68    zx_status_t st = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &proto);
69    if (st != ZX_OK) {
70        return st;
71    }
72
73    // Allocate a new device object for the bus.
74    pl031_t* pl031 = calloc(1, sizeof(*pl031));
75    if (!pl031) {
76        zxlogf(ERROR, "pl031_rtc: bind failed to allocate usb_dwc struct\n");
77        return ZX_ERR_NO_MEMORY;
78    }
79
80    // Carve out some address space for this device.
81    size_t mmio_size;
82    zx_handle_t mmio_handle = ZX_HANDLE_INVALID;
83    st = pdev_map_mmio(&proto, 0, ZX_CACHE_POLICY_UNCACHED_DEVICE, (void**)&pl031->regs,
84                       &mmio_size, &mmio_handle);
85    if (st != ZX_OK) {
86        zxlogf(ERROR, "pl031_rtc: bind failed to pdev_map_mmio.\n");
87        goto error_return;
88    }
89
90    pl031->parent = parent;
91
92    // bind the device
93    device_add_args_t args = {
94        .version = DEVICE_ADD_ARGS_VERSION,
95        .name = "rtc",
96        .ops = &pl031_rtc_device_proto,
97    };
98
99    zx_device_t* dev;
100    st = device_add(parent, &args, &dev);
101    if (st != ZX_OK) {
102        zxlogf(ERROR, "pl031_rtc: error adding device\n");
103        goto error_return;
104    }
105
106    // set the current RTC offset in the kernel
107    pl031_set_kernel_offset(pl031);
108
109    return ZX_OK;
110
111error_return:
112    if (pl031->regs) {
113        zx_vmar_unmap(zx_vmar_root_self(), (uintptr_t)pl031->regs, mmio_size);
114    }
115    zx_handle_close(mmio_handle);
116    if (pl031) {
117        free(pl031);
118    }
119
120    return st;
121}
122
123static zx_driver_ops_t pl031_rtc_driver_ops = {
124    .version = DRIVER_OPS_VERSION,
125    .bind = pl031_rtc_bind,
126};
127
128// The formatter does not play nice with these macros.
129// clang-format off
130ZIRCON_DRIVER_BEGIN(pl031, pl031_rtc_driver_ops, "zircon", "0.1", 3)
131    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_GENERIC),
132    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_GENERIC),
133    BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_RTC_PL031),
134ZIRCON_DRIVER_END(pl031)
135    // clang-format on
136