You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
qemu/hw/timer/renesas_tmr.c

494 lines
14 KiB
C

/*
* 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 CSS_EXTERNAL 0x00
#define CSS_INTERNAL 0x01
#define CSS_INVALID 0x02
#define CSS_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) == 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 {
/* disable 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;
switch (FIELD_EX8(tmr->tccr[1], TCCR, CSS)) {
case CSS_INTERNAL:
/* timer1 count update */
elapsed = elapsed_time(tmr, 1, delta);
if (elapsed >= 0x100) {
ovf = elapsed >> 8;
}
tcnt[1] = tmr->tcnt[1] + (elapsed & 0xff);
break;
case CSS_INVALID: /* guest error to have set this */
case CSS_EXTERNAL: /* QEMU doesn't implement these */
case CSS_CASCADING:
tcnt[1] = tmr->tcnt[1];
break;
default:
g_assert_not_reached();
}
switch (FIELD_EX8(tmr->tccr[0], TCCR, CSS)) {
case CSS_INTERNAL:
elapsed = elapsed_time(tmr, 0, delta);
tcnt[0] = tmr->tcnt[0] + elapsed;
break;
case CSS_CASCADING:
tcnt[0] = tmr->tcnt[0] + ovf;
break;
case CSS_INVALID: /* guest error to have set this */
case CSS_EXTERNAL: /* QEMU doesn't implement this */
tcnt[0] = tmr->tcnt[0];
break;
default:
g_assert_not_reached();
}
} 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);
}
/* fall through */
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) == 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) != 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 = (const 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;
device_class_set_legacy_reset(dc, 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)