| /* |
| * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) |
| * |
| * SPDX-License-Identifier: BSD-2-Clause |
| */ |
| |
| #include <errno.h> |
| #include <platsupport/io.h> |
| #include <platsupport/plat/pit.h> |
| |
| #include <inttypes.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| #include <utils/util.h> |
| |
| #define PIT_IOPORT_CHANNEL(x) (0x40 + x) /* valid channels are 0, 1, 2. we'll be using 0 exclusively, though */ |
| #define PIT_IOPORT_COMMAND 0x43 |
| #define PIT_IOPORT_PITCR PIT_IOPORT_COMMAND |
| |
| /* PIT command register macros */ |
| #define PITCR_SET_CHANNEL(x, channel) (((channel) << 6) | x) |
| #define PITCR_SET_OP_MODE(x, mode) (((mode) << 1) | x) |
| #define PITCR_SET_ACCESS_MODE(x, mode) (((mode) << 4) | x) |
| |
| #define PITCR_LATCH_CHANNEL(channel) PITCR_SET_CHANNEL(0, channel) |
| |
| #define PITCR_ACCESS_LOW 0x1 |
| #define PITCR_ACCESS_HIGH 0x2 |
| #define PITCR_ACCESS_LOHI 0x3 |
| |
| #define PITCR_MODE_ONESHOT 0x0 |
| #define PITCR_MODE_PERIODIC 0x2 |
| #define PITCR_MODE_SQUARE 0x3 |
| #define PITCR_MODE_SWSTROBE 0x4 |
| |
| /* helper functions */ |
| static inline int |
| set_pit_mode(ps_io_port_ops_t *ops, uint8_t channel, uint8_t mode) |
| { |
| return ps_io_port_out(ops, PIT_IOPORT_PITCR, 1, |
| PITCR_SET_CHANNEL(PITCR_SET_OP_MODE(PITCR_SET_ACCESS_MODE(0, PITCR_ACCESS_LOHI), mode), channel)); |
| } |
| |
| static inline int |
| configure_pit(pit_t *pit, uint8_t mode, uint64_t ns) |
| { |
| |
| int error; |
| |
| if (ns > PIT_MAX_NS || ns < PIT_MIN_NS) { |
| ZF_LOGV("ns invalid for programming PIT %"PRIu64" <= %"PRIu64" <= %"PRIu64"\n", |
| (uint64_t)PIT_MIN_NS, ns, (uint64_t)PIT_MAX_NS); |
| return EINVAL; |
| } |
| |
| uint64_t ticks = PIT_NS_TO_TICKS(ns); |
| |
| /* configure correct mode */ |
| error = set_pit_mode(&pit->ops, 0, mode); |
| if (error) { |
| ZF_LOGE("ps_io_port_out failed on channel %u\n", PIT_IOPORT_CHANNEL(0)); |
| return EIO; |
| } |
| |
| /* program timeout */ |
| error = ps_io_port_out(&pit->ops, PIT_IOPORT_CHANNEL(0), 1, (uint8_t) (ticks & 0xFF)); |
| if (error) { |
| ZF_LOGE("ps_io_port_out failed on channel %u\n", PIT_IOPORT_CHANNEL(0)); |
| return EIO; |
| } |
| |
| error = ps_io_port_out(&pit->ops, PIT_IOPORT_CHANNEL(0), 1, (uint8_t) (ticks >> 8) & 0xFF); |
| if (error) { |
| ZF_LOGE("ps_io_port_out failed on channel %u\n", PIT_IOPORT_CHANNEL(0)); |
| return EIO; |
| } |
| |
| return 0; |
| } |
| |
| /* interface functions */ |
| |
| int pit_cancel_timeout(pit_t *pit) |
| { |
| /* There's no way to disable the PIT, so we set it up in mode 0 and don't |
| * start it |
| */ |
| return set_pit_mode(&pit->ops, 0, PITCR_MODE_ONESHOT); |
| } |
| |
| uint64_t pit_get_time(pit_t *pit) |
| { |
| int error = ps_io_port_out(&pit->ops, PIT_IOPORT_CHANNEL(3), 1, 0); |
| if (error) { |
| return 0; |
| } |
| |
| uint32_t low, high; |
| |
| /* Read the low 8 bits of the current timer value. */ |
| error = ps_io_port_in(&pit->ops, PIT_IOPORT_CHANNEL(0), 1, &low); |
| if (error) { |
| return 0; |
| } |
| |
| /* Read the high 8 bits of the current timer value. */ |
| error = ps_io_port_in(&pit->ops, PIT_IOPORT_CHANNEL(0), 1, &high); |
| if (error) { |
| return 0; |
| } |
| |
| /* Assemble the high and low 8 bits using (high << 8) + low, and then convert to nanoseconds. */ |
| return ((high << 8) + low) * NS_IN_S / TICKS_PER_SECOND; |
| } |
| |
| int pit_set_timeout(pit_t *pit, uint64_t ns, bool periodic) |
| { |
| uint32_t mode = periodic ? PITCR_MODE_PERIODIC : PITCR_MODE_ONESHOT; |
| return configure_pit(pit, mode, ns); |
| } |
| |
| /* initialisation function */ |
| int pit_init(pit_t *pit, ps_io_port_ops_t io_port_ops) |
| { |
| if (pit == NULL) { |
| return EINVAL; |
| } |
| |
| /* check that the io port ops are at least implemented as these are absolutely required */ |
| if (!io_port_ops.io_port_in_fn || !io_port_ops.io_port_out_fn) { |
| return EINVAL; |
| } |
| |
| pit->ops = io_port_ops; |
| return 0; |
| } |