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