hw/timer: RX62N 8-Bit timer (TMR)
renesas_tmr: 8bit timer modules. This part use many renesas's CPU. Hardware manual. https://www.renesas.com/us/en/doc/products/mpumcu/doc/rx_family/r01uh0033ej0140_rx62n.pdf Signed-off-by: Yoshinori Sato <ysato@users.sourceforge.jp> Reviewed-by: Alex Bennée <alex.bennee@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-Id: <20200224141923.82118-16-ysato@users.sourceforge.jp> [PMD: Split from CMT, filled VMStateField for migration] Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
This commit is contained in:
parent
e78597cc45
commit
7adca78eda
|
@ -1972,8 +1972,10 @@ M: Yoshinori Sato <ysato@users.sourceforge.jp>
|
||||||
R: Magnus Damm <magnus.damm@gmail.com>
|
R: Magnus Damm <magnus.damm@gmail.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: hw/char/sh_serial.c
|
F: hw/char/sh_serial.c
|
||||||
|
F: hw/timer/renesas_tmr.c
|
||||||
F: hw/timer/sh_timer.c
|
F: hw/timer/sh_timer.c
|
||||||
F: include/hw/sh4/sh.h
|
F: include/hw/sh4/sh.h
|
||||||
|
F: include/hw/timer/renesas_tmr.h
|
||||||
|
|
||||||
Renesas RX peripherals
|
Renesas RX peripherals
|
||||||
M: Yoshinori Sato <ysato@users.sourceforge.jp>
|
M: Yoshinori Sato <ysato@users.sourceforge.jp>
|
||||||
|
|
|
@ -35,3 +35,6 @@ config CMSDK_APB_TIMER
|
||||||
config CMSDK_APB_DUALTIMER
|
config CMSDK_APB_DUALTIMER
|
||||||
bool
|
bool
|
||||||
select PTIMER
|
select PTIMER
|
||||||
|
|
||||||
|
config RENESAS_TMR
|
||||||
|
bool
|
||||||
|
|
|
@ -23,6 +23,7 @@ common-obj-$(CONFIG_OMAP) += omap_gptimer.o
|
||||||
common-obj-$(CONFIG_OMAP) += omap_synctimer.o
|
common-obj-$(CONFIG_OMAP) += omap_synctimer.o
|
||||||
common-obj-$(CONFIG_PXA2XX) += pxa2xx_timer.o
|
common-obj-$(CONFIG_PXA2XX) += pxa2xx_timer.o
|
||||||
common-obj-$(CONFIG_SH4) += sh_timer.o
|
common-obj-$(CONFIG_SH4) += sh_timer.o
|
||||||
|
common-obj-$(CONFIG_RENESAS_TMR) += renesas_tmr.o
|
||||||
common-obj-$(CONFIG_DIGIC) += digic-timer.o
|
common-obj-$(CONFIG_DIGIC) += digic-timer.o
|
||||||
common-obj-$(CONFIG_MIPS_CPS) += mips_gictimer.o
|
common-obj-$(CONFIG_MIPS_CPS) += mips_gictimer.o
|
||||||
|
|
||||||
|
|
477
hw/timer/renesas_tmr.c
Normal file
477
hw/timer/renesas_tmr.c
Normal file
|
@ -0,0 +1,477 @@
|
||||||
|
/*
|
||||||
|
* Renesas 8bit timer
|
||||||
|
*
|
||||||
|
* Datasheet: RX62N Group, RX621 Group User's Manual: Hardware
|
||||||
|
* (Rev.1.40 R01UH0033EJ0140)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Yoshinori Sato
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2 or later, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qemu/osdep.h"
|
||||||
|
#include "qemu/log.h"
|
||||||
|
#include "hw/irq.h"
|
||||||
|
#include "hw/registerfields.h"
|
||||||
|
#include "hw/qdev-properties.h"
|
||||||
|
#include "hw/timer/renesas_tmr.h"
|
||||||
|
#include "migration/vmstate.h"
|
||||||
|
|
||||||
|
REG8(TCR, 0)
|
||||||
|
FIELD(TCR, CCLR, 3, 2)
|
||||||
|
FIELD(TCR, OVIE, 5, 1)
|
||||||
|
FIELD(TCR, CMIEA, 6, 1)
|
||||||
|
FIELD(TCR, CMIEB, 7, 1)
|
||||||
|
REG8(TCSR, 2)
|
||||||
|
FIELD(TCSR, OSA, 0, 2)
|
||||||
|
FIELD(TCSR, OSB, 2, 2)
|
||||||
|
FIELD(TCSR, ADTE, 4, 2)
|
||||||
|
REG8(TCORA, 4)
|
||||||
|
REG8(TCORB, 6)
|
||||||
|
REG8(TCNT, 8)
|
||||||
|
REG8(TCCR, 10)
|
||||||
|
FIELD(TCCR, CKS, 0, 3)
|
||||||
|
FIELD(TCCR, CSS, 3, 2)
|
||||||
|
FIELD(TCCR, TMRIS, 7, 1)
|
||||||
|
|
||||||
|
#define INTERNAL 0x01
|
||||||
|
#define CASCADING 0x03
|
||||||
|
#define CCLR_A 0x01
|
||||||
|
#define CCLR_B 0x02
|
||||||
|
|
||||||
|
static const int clkdiv[] = {0, 1, 2, 8, 32, 64, 1024, 8192};
|
||||||
|
|
||||||
|
static uint8_t concat_reg(uint8_t *reg)
|
||||||
|
{
|
||||||
|
return (reg[0] << 8) | reg[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_events(RTMRState *tmr, int ch)
|
||||||
|
{
|
||||||
|
uint16_t diff[TMR_NR_EVENTS], min;
|
||||||
|
int64_t next_time;
|
||||||
|
int i, event;
|
||||||
|
|
||||||
|
if (tmr->tccr[ch] == 0) {
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
if (FIELD_EX8(tmr->tccr[ch], TCCR, CSS) == 0) {
|
||||||
|
/* external clock mode */
|
||||||
|
/* event not happened */
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) == CASCADING) {
|
||||||
|
/* cascading mode */
|
||||||
|
if (ch == 1) {
|
||||||
|
tmr->next[ch] = none;
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
diff[cmia] = concat_reg(tmr->tcora) - concat_reg(tmr->tcnt);
|
||||||
|
diff[cmib] = concat_reg(tmr->tcorb) - concat_reg(tmr->tcnt);
|
||||||
|
diff[ovi] = 0x10000 - concat_reg(tmr->tcnt);
|
||||||
|
} else {
|
||||||
|
/* separate mode */
|
||||||
|
diff[cmia] = tmr->tcora[ch] - tmr->tcnt[ch];
|
||||||
|
diff[cmib] = tmr->tcorb[ch] - tmr->tcnt[ch];
|
||||||
|
diff[ovi] = 0x100 - tmr->tcnt[ch];
|
||||||
|
}
|
||||||
|
/* Search for the most recently occurring event. */
|
||||||
|
for (event = 0, min = diff[0], i = 1; i < none; i++) {
|
||||||
|
if (min > diff[i]) {
|
||||||
|
event = i;
|
||||||
|
min = diff[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tmr->next[ch] = event;
|
||||||
|
next_time = diff[event];
|
||||||
|
next_time *= clkdiv[FIELD_EX8(tmr->tccr[ch], TCCR, CKS)];
|
||||||
|
next_time *= NANOSECONDS_PER_SECOND;
|
||||||
|
next_time /= tmr->input_freq;
|
||||||
|
next_time += qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||||
|
timer_mod(&tmr->timer[ch], next_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int elapsed_time(RTMRState *tmr, int ch, int64_t delta)
|
||||||
|
{
|
||||||
|
int divrate = clkdiv[FIELD_EX8(tmr->tccr[ch], TCCR, CKS)];
|
||||||
|
int et;
|
||||||
|
|
||||||
|
tmr->div_round[ch] += delta;
|
||||||
|
if (divrate > 0) {
|
||||||
|
et = tmr->div_round[ch] / divrate;
|
||||||
|
tmr->div_round[ch] %= divrate;
|
||||||
|
} else {
|
||||||
|
/* disble clock. so no update */
|
||||||
|
et = 0;
|
||||||
|
}
|
||||||
|
return et;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t read_tcnt(RTMRState *tmr, unsigned size, int ch)
|
||||||
|
{
|
||||||
|
int64_t delta, now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||||
|
int elapsed, ovf = 0;
|
||||||
|
uint16_t tcnt[2];
|
||||||
|
uint32_t ret;
|
||||||
|
|
||||||
|
delta = (now - tmr->tick) * NANOSECONDS_PER_SECOND / tmr->input_freq;
|
||||||
|
if (delta > 0) {
|
||||||
|
tmr->tick = now;
|
||||||
|
|
||||||
|
if (FIELD_EX8(tmr->tccr[1], TCCR, CSS) == INTERNAL) {
|
||||||
|
/* timer1 count update */
|
||||||
|
elapsed = elapsed_time(tmr, 1, delta);
|
||||||
|
if (elapsed >= 0x100) {
|
||||||
|
ovf = elapsed >> 8;
|
||||||
|
}
|
||||||
|
tcnt[1] = tmr->tcnt[1] + (elapsed & 0xff);
|
||||||
|
}
|
||||||
|
switch (FIELD_EX8(tmr->tccr[0], TCCR, CSS)) {
|
||||||
|
case INTERNAL:
|
||||||
|
elapsed = elapsed_time(tmr, 0, delta);
|
||||||
|
tcnt[0] = tmr->tcnt[0] + elapsed;
|
||||||
|
break;
|
||||||
|
case CASCADING:
|
||||||
|
if (ovf > 0) {
|
||||||
|
tcnt[0] = tmr->tcnt[0] + ovf;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tcnt[0] = tmr->tcnt[0];
|
||||||
|
tcnt[1] = tmr->tcnt[1];
|
||||||
|
}
|
||||||
|
if (size == 1) {
|
||||||
|
return tcnt[ch];
|
||||||
|
} else {
|
||||||
|
ret = 0;
|
||||||
|
ret = deposit32(ret, 0, 8, tcnt[1]);
|
||||||
|
ret = deposit32(ret, 8, 8, tcnt[0]);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t read_tccr(uint8_t r)
|
||||||
|
{
|
||||||
|
uint8_t tccr = 0;
|
||||||
|
tccr = FIELD_DP8(tccr, TCCR, TMRIS,
|
||||||
|
FIELD_EX8(r, TCCR, TMRIS));
|
||||||
|
tccr = FIELD_DP8(tccr, TCCR, CSS,
|
||||||
|
FIELD_EX8(r, TCCR, CSS));
|
||||||
|
tccr = FIELD_DP8(tccr, TCCR, CKS,
|
||||||
|
FIELD_EX8(r, TCCR, CKS));
|
||||||
|
return tccr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t tmr_read(void *opaque, hwaddr addr, unsigned size)
|
||||||
|
{
|
||||||
|
RTMRState *tmr = opaque;
|
||||||
|
int ch = addr & 1;
|
||||||
|
uint64_t ret;
|
||||||
|
|
||||||
|
if (size == 2 && (ch != 0 || addr == A_TCR || addr == A_TCSR)) {
|
||||||
|
qemu_log_mask(LOG_GUEST_ERROR, "renesas_tmr: Invalid read size 0x%"
|
||||||
|
HWADDR_PRIX "\n",
|
||||||
|
addr);
|
||||||
|
return UINT64_MAX;
|
||||||
|
}
|
||||||
|
switch (addr & 0x0e) {
|
||||||
|
case A_TCR:
|
||||||
|
ret = 0;
|
||||||
|
ret = FIELD_DP8(ret, TCR, CCLR,
|
||||||
|
FIELD_EX8(tmr->tcr[ch], TCR, CCLR));
|
||||||
|
ret = FIELD_DP8(ret, TCR, OVIE,
|
||||||
|
FIELD_EX8(tmr->tcr[ch], TCR, OVIE));
|
||||||
|
ret = FIELD_DP8(ret, TCR, CMIEA,
|
||||||
|
FIELD_EX8(tmr->tcr[ch], TCR, CMIEA));
|
||||||
|
ret = FIELD_DP8(ret, TCR, CMIEB,
|
||||||
|
FIELD_EX8(tmr->tcr[ch], TCR, CMIEB));
|
||||||
|
return ret;
|
||||||
|
case A_TCSR:
|
||||||
|
ret = 0;
|
||||||
|
ret = FIELD_DP8(ret, TCSR, OSA,
|
||||||
|
FIELD_EX8(tmr->tcsr[ch], TCSR, OSA));
|
||||||
|
ret = FIELD_DP8(ret, TCSR, OSB,
|
||||||
|
FIELD_EX8(tmr->tcsr[ch], TCSR, OSB));
|
||||||
|
switch (ch) {
|
||||||
|
case 0:
|
||||||
|
ret = FIELD_DP8(ret, TCSR, ADTE,
|
||||||
|
FIELD_EX8(tmr->tcsr[ch], TCSR, ADTE));
|
||||||
|
break;
|
||||||
|
case 1: /* CH1 ADTE unimplement always 1 */
|
||||||
|
ret = FIELD_DP8(ret, TCSR, ADTE, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
case A_TCORA:
|
||||||
|
if (size == 1) {
|
||||||
|
return tmr->tcora[ch];
|
||||||
|
} else if (ch == 0) {
|
||||||
|
return concat_reg(tmr->tcora);
|
||||||
|
}
|
||||||
|
case A_TCORB:
|
||||||
|
if (size == 1) {
|
||||||
|
return tmr->tcorb[ch];
|
||||||
|
} else {
|
||||||
|
return concat_reg(tmr->tcorb);
|
||||||
|
}
|
||||||
|
case A_TCNT:
|
||||||
|
return read_tcnt(tmr, size, ch);
|
||||||
|
case A_TCCR:
|
||||||
|
if (size == 1) {
|
||||||
|
return read_tccr(tmr->tccr[ch]);
|
||||||
|
} else {
|
||||||
|
return read_tccr(tmr->tccr[0]) << 8 | read_tccr(tmr->tccr[1]);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
qemu_log_mask(LOG_UNIMP, "renesas_tmr: Register 0x%" HWADDR_PRIX
|
||||||
|
" not implemented\n",
|
||||||
|
addr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return UINT64_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tmr_write_count(RTMRState *tmr, int ch, unsigned size,
|
||||||
|
uint8_t *reg, uint64_t val)
|
||||||
|
{
|
||||||
|
if (size == 1) {
|
||||||
|
reg[ch] = val;
|
||||||
|
update_events(tmr, ch);
|
||||||
|
} else {
|
||||||
|
reg[0] = extract32(val, 8, 8);
|
||||||
|
reg[1] = extract32(val, 0, 8);
|
||||||
|
update_events(tmr, 0);
|
||||||
|
update_events(tmr, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tmr_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
|
||||||
|
{
|
||||||
|
RTMRState *tmr = opaque;
|
||||||
|
int ch = addr & 1;
|
||||||
|
|
||||||
|
if (size == 2 && (ch != 0 || addr == A_TCR || addr == A_TCSR)) {
|
||||||
|
qemu_log_mask(LOG_GUEST_ERROR,
|
||||||
|
"renesas_tmr: Invalid write size 0x%" HWADDR_PRIX "\n",
|
||||||
|
addr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (addr & 0x0e) {
|
||||||
|
case A_TCR:
|
||||||
|
tmr->tcr[ch] = val;
|
||||||
|
break;
|
||||||
|
case A_TCSR:
|
||||||
|
tmr->tcsr[ch] = val;
|
||||||
|
break;
|
||||||
|
case A_TCORA:
|
||||||
|
tmr_write_count(tmr, ch, size, tmr->tcora, val);
|
||||||
|
break;
|
||||||
|
case A_TCORB:
|
||||||
|
tmr_write_count(tmr, ch, size, tmr->tcorb, val);
|
||||||
|
break;
|
||||||
|
case A_TCNT:
|
||||||
|
tmr_write_count(tmr, ch, size, tmr->tcnt, val);
|
||||||
|
break;
|
||||||
|
case A_TCCR:
|
||||||
|
tmr_write_count(tmr, ch, size, tmr->tccr, val);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qemu_log_mask(LOG_UNIMP, "renesas_tmr: Register 0x%" HWADDR_PRIX
|
||||||
|
" not implemented\n",
|
||||||
|
addr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const MemoryRegionOps tmr_ops = {
|
||||||
|
.write = tmr_write,
|
||||||
|
.read = tmr_read,
|
||||||
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||||
|
.impl = {
|
||||||
|
.min_access_size = 1,
|
||||||
|
.max_access_size = 2,
|
||||||
|
},
|
||||||
|
.valid = {
|
||||||
|
.min_access_size = 1,
|
||||||
|
.max_access_size = 2,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static void timer_events(RTMRState *tmr, int ch);
|
||||||
|
|
||||||
|
static uint16_t issue_event(RTMRState *tmr, int ch, int sz,
|
||||||
|
uint16_t tcnt, uint16_t tcora, uint16_t tcorb)
|
||||||
|
{
|
||||||
|
uint16_t ret = tcnt;
|
||||||
|
|
||||||
|
switch (tmr->next[ch]) {
|
||||||
|
case none:
|
||||||
|
break;
|
||||||
|
case cmia:
|
||||||
|
if (tcnt >= tcora) {
|
||||||
|
if (FIELD_EX8(tmr->tcr[ch], TCR, CCLR) == CCLR_A) {
|
||||||
|
ret = tcnt - tcora;
|
||||||
|
}
|
||||||
|
if (FIELD_EX8(tmr->tcr[ch], TCR, CMIEA)) {
|
||||||
|
qemu_irq_pulse(tmr->cmia[ch]);
|
||||||
|
}
|
||||||
|
if (sz == 8 && ch == 0 &&
|
||||||
|
FIELD_EX8(tmr->tccr[1], TCCR, CSS) == CASCADING) {
|
||||||
|
tmr->tcnt[1]++;
|
||||||
|
timer_events(tmr, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case cmib:
|
||||||
|
if (tcnt >= tcorb) {
|
||||||
|
if (FIELD_EX8(tmr->tcr[ch], TCR, CCLR) == CCLR_B) {
|
||||||
|
ret = tcnt - tcorb;
|
||||||
|
}
|
||||||
|
if (FIELD_EX8(tmr->tcr[ch], TCR, CMIEB)) {
|
||||||
|
qemu_irq_pulse(tmr->cmib[ch]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ovi:
|
||||||
|
if ((tcnt >= (1 << sz)) && FIELD_EX8(tmr->tcr[ch], TCR, OVIE)) {
|
||||||
|
qemu_irq_pulse(tmr->ovi[ch]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
g_assert_not_reached();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void timer_events(RTMRState *tmr, int ch)
|
||||||
|
{
|
||||||
|
uint16_t tcnt;
|
||||||
|
|
||||||
|
tmr->tcnt[ch] = read_tcnt(tmr, 1, ch);
|
||||||
|
if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) != CASCADING) {
|
||||||
|
tmr->tcnt[ch] = issue_event(tmr, ch, 8,
|
||||||
|
tmr->tcnt[ch],
|
||||||
|
tmr->tcora[ch],
|
||||||
|
tmr->tcorb[ch]) & 0xff;
|
||||||
|
} else {
|
||||||
|
if (ch == 1) {
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
tcnt = issue_event(tmr, ch, 16,
|
||||||
|
concat_reg(tmr->tcnt),
|
||||||
|
concat_reg(tmr->tcora),
|
||||||
|
concat_reg(tmr->tcorb));
|
||||||
|
tmr->tcnt[0] = (tcnt >> 8) & 0xff;
|
||||||
|
tmr->tcnt[1] = tcnt & 0xff;
|
||||||
|
}
|
||||||
|
update_events(tmr, ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void timer_event0(void *opaque)
|
||||||
|
{
|
||||||
|
RTMRState *tmr = opaque;
|
||||||
|
|
||||||
|
timer_events(tmr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void timer_event1(void *opaque)
|
||||||
|
{
|
||||||
|
RTMRState *tmr = opaque;
|
||||||
|
|
||||||
|
timer_events(tmr, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rtmr_reset(DeviceState *dev)
|
||||||
|
{
|
||||||
|
RTMRState *tmr = RTMR(dev);
|
||||||
|
tmr->tcr[0] = tmr->tcr[1] = 0x00;
|
||||||
|
tmr->tcsr[0] = 0x00;
|
||||||
|
tmr->tcsr[1] = 0x10;
|
||||||
|
tmr->tcnt[0] = tmr->tcnt[1] = 0x00;
|
||||||
|
tmr->tcora[0] = tmr->tcora[1] = 0xff;
|
||||||
|
tmr->tcorb[0] = tmr->tcorb[1] = 0xff;
|
||||||
|
tmr->tccr[0] = tmr->tccr[1] = 0x00;
|
||||||
|
tmr->next[0] = tmr->next[1] = none;
|
||||||
|
tmr->tick = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rtmr_init(Object *obj)
|
||||||
|
{
|
||||||
|
SysBusDevice *d = SYS_BUS_DEVICE(obj);
|
||||||
|
RTMRState *tmr = RTMR(obj);
|
||||||
|
int i;
|
||||||
|
|
||||||
|
memory_region_init_io(&tmr->memory, OBJECT(tmr), &tmr_ops,
|
||||||
|
tmr, "renesas-tmr", 0x10);
|
||||||
|
sysbus_init_mmio(d, &tmr->memory);
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(tmr->ovi); i++) {
|
||||||
|
sysbus_init_irq(d, &tmr->cmia[i]);
|
||||||
|
sysbus_init_irq(d, &tmr->cmib[i]);
|
||||||
|
sysbus_init_irq(d, &tmr->ovi[i]);
|
||||||
|
}
|
||||||
|
timer_init_ns(&tmr->timer[0], QEMU_CLOCK_VIRTUAL, timer_event0, tmr);
|
||||||
|
timer_init_ns(&tmr->timer[1], QEMU_CLOCK_VIRTUAL, timer_event1, tmr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const VMStateDescription vmstate_rtmr = {
|
||||||
|
.name = "rx-tmr",
|
||||||
|
.version_id = 1,
|
||||||
|
.minimum_version_id = 1,
|
||||||
|
.fields = (VMStateField[]) {
|
||||||
|
VMSTATE_INT64(tick, RTMRState),
|
||||||
|
VMSTATE_UINT8_ARRAY(tcnt, RTMRState, TMR_CH),
|
||||||
|
VMSTATE_UINT8_ARRAY(tcora, RTMRState, TMR_CH),
|
||||||
|
VMSTATE_UINT8_ARRAY(tcorb, RTMRState, TMR_CH),
|
||||||
|
VMSTATE_UINT8_ARRAY(tcr, RTMRState, TMR_CH),
|
||||||
|
VMSTATE_UINT8_ARRAY(tccr, RTMRState, TMR_CH),
|
||||||
|
VMSTATE_UINT8_ARRAY(tcor, RTMRState, TMR_CH),
|
||||||
|
VMSTATE_UINT8_ARRAY(tcsr, RTMRState, TMR_CH),
|
||||||
|
VMSTATE_INT64_ARRAY(div_round, RTMRState, TMR_CH),
|
||||||
|
VMSTATE_UINT8_ARRAY(next, RTMRState, TMR_CH),
|
||||||
|
VMSTATE_TIMER_ARRAY(timer, RTMRState, TMR_CH),
|
||||||
|
VMSTATE_END_OF_LIST()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static Property rtmr_properties[] = {
|
||||||
|
DEFINE_PROP_UINT64("input-freq", RTMRState, input_freq, 0),
|
||||||
|
DEFINE_PROP_END_OF_LIST(),
|
||||||
|
};
|
||||||
|
|
||||||
|
static void rtmr_class_init(ObjectClass *klass, void *data)
|
||||||
|
{
|
||||||
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||||
|
|
||||||
|
dc->vmsd = &vmstate_rtmr;
|
||||||
|
dc->reset = rtmr_reset;
|
||||||
|
device_class_set_props(dc, rtmr_properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const TypeInfo rtmr_info = {
|
||||||
|
.name = TYPE_RENESAS_TMR,
|
||||||
|
.parent = TYPE_SYS_BUS_DEVICE,
|
||||||
|
.instance_size = sizeof(RTMRState),
|
||||||
|
.instance_init = rtmr_init,
|
||||||
|
.class_init = rtmr_class_init,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void rtmr_register_types(void)
|
||||||
|
{
|
||||||
|
type_register_static(&rtmr_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
type_init(rtmr_register_types)
|
55
include/hw/timer/renesas_tmr.h
Normal file
55
include/hw/timer/renesas_tmr.h
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Renesas 8bit timer Object
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 Yoshinori Sato
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef HW_TIMER_RENESAS_TMR_H
|
||||||
|
#define HW_TIMER_RENESAS_TMR_H
|
||||||
|
|
||||||
|
#include "qemu/timer.h"
|
||||||
|
#include "hw/sysbus.h"
|
||||||
|
|
||||||
|
#define TYPE_RENESAS_TMR "renesas-tmr"
|
||||||
|
#define RTMR(obj) OBJECT_CHECK(RTMRState, (obj), TYPE_RENESAS_TMR)
|
||||||
|
|
||||||
|
enum timer_event {
|
||||||
|
cmia = 0,
|
||||||
|
cmib = 1,
|
||||||
|
ovi = 2,
|
||||||
|
none = 3,
|
||||||
|
TMR_NR_EVENTS = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
TMR_CH = 2,
|
||||||
|
TMR_NR_IRQ = 3 * TMR_CH
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct RTMRState {
|
||||||
|
/*< private >*/
|
||||||
|
SysBusDevice parent_obj;
|
||||||
|
/*< public >*/
|
||||||
|
|
||||||
|
uint64_t input_freq;
|
||||||
|
MemoryRegion memory;
|
||||||
|
|
||||||
|
int64_t tick;
|
||||||
|
uint8_t tcnt[TMR_CH];
|
||||||
|
uint8_t tcora[TMR_CH];
|
||||||
|
uint8_t tcorb[TMR_CH];
|
||||||
|
uint8_t tcr[TMR_CH];
|
||||||
|
uint8_t tccr[TMR_CH];
|
||||||
|
uint8_t tcor[TMR_CH];
|
||||||
|
uint8_t tcsr[TMR_CH];
|
||||||
|
int64_t div_round[TMR_CH];
|
||||||
|
uint8_t next[TMR_CH];
|
||||||
|
qemu_irq cmia[TMR_CH];
|
||||||
|
qemu_irq cmib[TMR_CH];
|
||||||
|
qemu_irq ovi[TMR_CH];
|
||||||
|
QEMUTimer timer[TMR_CH];
|
||||||
|
} RTMRState;
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in a new issue