| /* Blackfin Core Timer model. |
| |
| Copyright (C) 2010-2021 Free Software Foundation, Inc. |
| Contributed by Analog Devices, Inc. |
| |
| This file is part of simulators. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
| |
| /* This must come before any other includes. */ |
| #include "defs.h" |
| |
| #include "sim-main.h" |
| #include "devices.h" |
| #include "dv-bfin_cec.h" |
| #include "dv-bfin_ctimer.h" |
| |
| struct bfin_ctimer |
| { |
| bu32 base; |
| struct hw_event *handler; |
| signed64 timeout; |
| |
| /* Order after here is important -- matches hardware MMR layout. */ |
| bu32 tcntl, tperiod, tscale, tcount; |
| }; |
| #define mmr_base() offsetof(struct bfin_ctimer, tcntl) |
| #define mmr_offset(mmr) (offsetof(struct bfin_ctimer, mmr) - mmr_base()) |
| |
| static const char * const mmr_names[] = |
| { |
| "TCNTL", "TPERIOD", "TSCALE", "TCOUNT", |
| }; |
| #define mmr_name(off) mmr_names[(off) / 4] |
| |
| static bool |
| bfin_ctimer_enabled (struct bfin_ctimer *ctimer) |
| { |
| return (ctimer->tcntl & TMPWR) && (ctimer->tcntl & TMREN); |
| } |
| |
| static bu32 |
| bfin_ctimer_scale (struct bfin_ctimer *ctimer) |
| { |
| /* Only low 8 bits are actually checked. */ |
| return (ctimer->tscale & 0xff) + 1; |
| } |
| |
| static void |
| bfin_ctimer_schedule (struct hw *me, struct bfin_ctimer *ctimer); |
| |
| static void |
| bfin_ctimer_expire (struct hw *me, void *data) |
| { |
| struct bfin_ctimer *ctimer = data; |
| |
| ctimer->tcntl |= TINT; |
| if (ctimer->tcntl & TAUTORLD) |
| { |
| ctimer->tcount = ctimer->tperiod; |
| bfin_ctimer_schedule (me, ctimer); |
| } |
| else |
| { |
| ctimer->tcount = 0; |
| ctimer->handler = NULL; |
| } |
| |
| hw_port_event (me, IVG_IVTMR, 1); |
| } |
| |
| static void |
| bfin_ctimer_update_count (struct hw *me, struct bfin_ctimer *ctimer) |
| { |
| bu32 scale, ticks; |
| signed64 timeout; |
| |
| /* If the timer was enabled w/out autoreload and has expired, then |
| there's nothing to calculate here. */ |
| if (ctimer->handler == NULL) |
| return; |
| |
| scale = bfin_ctimer_scale (ctimer); |
| timeout = hw_event_remain_time (me, ctimer->handler); |
| ticks = ctimer->timeout - timeout; |
| ctimer->tcount -= (scale * ticks); |
| ctimer->timeout = timeout; |
| } |
| |
| static void |
| bfin_ctimer_deschedule (struct hw *me, struct bfin_ctimer *ctimer) |
| { |
| if (ctimer->handler) |
| { |
| hw_event_queue_deschedule (me, ctimer->handler); |
| ctimer->handler = NULL; |
| } |
| } |
| |
| static void |
| bfin_ctimer_schedule (struct hw *me, struct bfin_ctimer *ctimer) |
| { |
| bu32 scale = bfin_ctimer_scale (ctimer); |
| ctimer->timeout = (ctimer->tcount / scale) + !!(ctimer->tcount % scale); |
| ctimer->handler = hw_event_queue_schedule (me, ctimer->timeout, |
| bfin_ctimer_expire, |
| ctimer); |
| } |
| |
| static unsigned |
| bfin_ctimer_io_write_buffer (struct hw *me, const void *source, |
| int space, address_word addr, unsigned nr_bytes) |
| { |
| struct bfin_ctimer *ctimer = hw_data (me); |
| bool curr_enabled; |
| bu32 mmr_off; |
| bu32 value; |
| bu32 *valuep; |
| |
| /* Invalid access mode is higher priority than missing register. */ |
| if (!dv_bfin_mmr_require_32 (me, addr, nr_bytes, true)) |
| return 0; |
| |
| value = dv_load_4 (source); |
| mmr_off = addr - ctimer->base; |
| valuep = (void *)((unsigned long)ctimer + mmr_base() + mmr_off); |
| |
| HW_TRACE_WRITE (); |
| |
| curr_enabled = bfin_ctimer_enabled (ctimer); |
| switch (mmr_off) |
| { |
| case mmr_offset(tcntl): |
| /* HRM describes TINT as sticky, but it isn't W1C. */ |
| *valuep = value; |
| |
| if (bfin_ctimer_enabled (ctimer) == curr_enabled) |
| { |
| /* Do nothing. */ |
| } |
| else if (curr_enabled) |
| { |
| bfin_ctimer_update_count (me, ctimer); |
| bfin_ctimer_deschedule (me, ctimer); |
| } |
| else |
| bfin_ctimer_schedule (me, ctimer); |
| |
| break; |
| case mmr_offset(tcount): |
| /* HRM says writes are discarded when enabled. */ |
| /* XXX: But hardware seems to be writeable all the time ? */ |
| /* if (!curr_enabled) */ |
| *valuep = value; |
| break; |
| case mmr_offset(tperiod): |
| /* HRM says writes are discarded when enabled. */ |
| /* XXX: But hardware seems to be writeable all the time ? */ |
| /* if (!curr_enabled) */ |
| { |
| /* Writes are mirrored into TCOUNT. */ |
| ctimer->tcount = value; |
| *valuep = value; |
| } |
| break; |
| case mmr_offset(tscale): |
| if (curr_enabled) |
| { |
| bfin_ctimer_update_count (me, ctimer); |
| bfin_ctimer_deschedule (me, ctimer); |
| } |
| *valuep = value; |
| if (curr_enabled) |
| bfin_ctimer_schedule (me, ctimer); |
| break; |
| } |
| |
| return nr_bytes; |
| } |
| |
| static unsigned |
| bfin_ctimer_io_read_buffer (struct hw *me, void *dest, |
| int space, address_word addr, unsigned nr_bytes) |
| { |
| struct bfin_ctimer *ctimer = hw_data (me); |
| bu32 mmr_off; |
| bu32 *valuep; |
| |
| /* Invalid access mode is higher priority than missing register. */ |
| if (!dv_bfin_mmr_require_32 (me, addr, nr_bytes, false)) |
| return 0; |
| |
| mmr_off = addr - ctimer->base; |
| valuep = (void *)((unsigned long)ctimer + mmr_base() + mmr_off); |
| |
| HW_TRACE_READ (); |
| |
| switch (mmr_off) |
| { |
| case mmr_offset(tcount): |
| /* Since we're optimizing events here, we need to calculate |
| the new tcount value. */ |
| if (bfin_ctimer_enabled (ctimer)) |
| bfin_ctimer_update_count (me, ctimer); |
| break; |
| } |
| |
| dv_store_4 (dest, *valuep); |
| |
| return nr_bytes; |
| } |
| |
| static const struct hw_port_descriptor bfin_ctimer_ports[] = |
| { |
| { "ivtmr", IVG_IVTMR, 0, output_port, }, |
| { NULL, 0, 0, 0, }, |
| }; |
| |
| static void |
| attach_bfin_ctimer_regs (struct hw *me, struct bfin_ctimer *ctimer) |
| { |
| address_word attach_address; |
| int attach_space; |
| unsigned attach_size; |
| reg_property_spec reg; |
| |
| if (hw_find_property (me, "reg") == NULL) |
| hw_abort (me, "Missing \"reg\" property"); |
| |
| if (!hw_find_reg_array_property (me, "reg", 0, ®)) |
| hw_abort (me, "\"reg\" property must contain three addr/size entries"); |
| |
| hw_unit_address_to_attach_address (hw_parent (me), |
| ®.address, |
| &attach_space, &attach_address, me); |
| hw_unit_size_to_attach_size (hw_parent (me), ®.size, &attach_size, me); |
| |
| if (attach_size != BFIN_COREMMR_CTIMER_SIZE) |
| hw_abort (me, "\"reg\" size must be %#x", BFIN_COREMMR_CTIMER_SIZE); |
| |
| hw_attach_address (hw_parent (me), |
| 0, attach_space, attach_address, attach_size, me); |
| |
| ctimer->base = attach_address; |
| } |
| |
| static void |
| bfin_ctimer_finish (struct hw *me) |
| { |
| struct bfin_ctimer *ctimer; |
| |
| ctimer = HW_ZALLOC (me, struct bfin_ctimer); |
| |
| set_hw_data (me, ctimer); |
| set_hw_io_read_buffer (me, bfin_ctimer_io_read_buffer); |
| set_hw_io_write_buffer (me, bfin_ctimer_io_write_buffer); |
| set_hw_ports (me, bfin_ctimer_ports); |
| |
| attach_bfin_ctimer_regs (me, ctimer); |
| |
| /* Initialize the Core Timer. */ |
| } |
| |
| const struct hw_descriptor dv_bfin_ctimer_descriptor[] = |
| { |
| {"bfin_ctimer", bfin_ctimer_finish,}, |
| {NULL, NULL}, |
| }; |