From patchwork Tue Feb 16 11:34:55 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Jeffery X-Patchwork-Id: 8325681 Return-Path: X-Original-To: patchwork-qemu-devel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 7C3189F38B for ; Tue, 16 Feb 2016 13:22:51 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 4821020254 for ; Tue, 16 Feb 2016 13:22:50 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 098482020F for ; Tue, 16 Feb 2016 13:22:49 +0000 (UTC) Received: from localhost ([::1]:45847 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aVfaa-00014Y-Dc for patchwork-qemu-devel@patchwork.kernel.org; Tue, 16 Feb 2016 08:22:48 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:42579) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aVduV-0002Bg-TW for qemu-devel@nongnu.org; Tue, 16 Feb 2016 06:35:20 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1aVduU-0004I9-4B for qemu-devel@nongnu.org; Tue, 16 Feb 2016 06:35:15 -0500 Received: from sub3.mail.dreamhost.com ([69.163.253.7]:40264 helo=homiemail-a20.g.dreamhost.com) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aVduO-0004Ga-OV; Tue, 16 Feb 2016 06:35:09 -0500 Received: from homiemail-a20.g.dreamhost.com (localhost [127.0.0.1]) by homiemail-a20.g.dreamhost.com (Postfix) with ESMTP id 2F26B7EC07D; Tue, 16 Feb 2016 03:35:08 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=aj.id.au; h=from:to:cc :subject:date:message-id:in-reply-to:references; s=aj.id.au; bh= CGzrxzs6lZOjLbzAKry8zUMsDY4=; b=H6JNPRgVWq4IiRU3mtSfMBDRaRkZiaoX kS6xG+aFbliHKH3lTwZ53mL01ewo6CNldx7HHoncuM1Bsb7N1w2AW0U5HcGvR0Cf lyZN/WW5TwrTXeoG5oXTI6ExkFgqg79+lBQvIymin2Q/V/OpG/boPO8mM/M1AbIH kLDPUcSL5Ds= Received: from keelia.au.ibm.com (ppp203-122-213-247.static.internode.on.net [203.122.213.247]) (using TLSv1 with cipher AES128-SHA (128/128 bits)) (No client certificate requested) (Authenticated sender: andrew@aj.id.au) by homiemail-a20.g.dreamhost.com (Postfix) with ESMTPSA id 063507EC060; Tue, 16 Feb 2016 03:35:06 -0800 (PST) From: Andrew Jeffery To: qemu-devel@nongnu.org Date: Tue, 16 Feb 2016 22:04:55 +1030 Message-Id: <1455622497-25966-2-git-send-email-andrew@aj.id.au> X-Mailer: git-send-email 2.5.0 In-Reply-To: <1455622497-25966-1-git-send-email-andrew@aj.id.au> References: <1455622497-25966-1-git-send-email-andrew@aj.id.au> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6.x X-Received-From: 69.163.253.7 X-Mailman-Approved-At: Tue, 16 Feb 2016 08:21:11 -0500 Cc: openbmc@lists.ozlabs.org, qemu-arm@nongnu.org Subject: [Qemu-devel] [PATCH v2 1/3] hw/timer: Add ASPEED AST2400 timer device model X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org X-Spam-Status: No, score=-6.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI, T_DKIM_INVALID, UNPARSEABLE_RELAY autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Implement basic AST2400 timer functionality: Timers can be configured, enabled, reset and disabled. A number of hardware features are not implemented: * Timer Overflow interrupts * Clock value matching * Pulse generation The implementation is enough to boot the Linux kernel configured with aspeed_defconfig. Signed-off-by: Andrew Jeffery --- default-configs/arm-softmmu.mak | 2 + hw/timer/Makefile.objs | 2 + hw/timer/aspeed_timer.c | 313 ++++++++++++++++++++++++++++++++++++++++ include/hw/timer/aspeed_timer.h | 55 +++++++ trace-events | 9 ++ 5 files changed, 381 insertions(+) create mode 100644 hw/timer/aspeed_timer.c create mode 100644 include/hw/timer/aspeed_timer.h diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak index a9f82a1..4072174 100644 --- a/default-configs/arm-softmmu.mak +++ b/default-configs/arm-softmmu.mak @@ -110,3 +110,5 @@ CONFIG_IOH3420=y CONFIG_I82801B11=y CONFIG_ACPI=y CONFIG_SMBIOS=y + +CONFIG_ASPEED_SOC=y diff --git a/hw/timer/Makefile.objs b/hw/timer/Makefile.objs index 133bd0d..f6f7351 100644 --- a/hw/timer/Makefile.objs +++ b/hw/timer/Makefile.objs @@ -33,3 +33,5 @@ obj-$(CONFIG_MC146818RTC) += mc146818rtc.o obj-$(CONFIG_ALLWINNER_A10_PIT) += allwinner-a10-pit.o common-obj-$(CONFIG_STM32F2XX_TIMER) += stm32f2xx_timer.o + +common-obj-$(CONFIG_ASPEED_SOC) += aspeed_timer.o diff --git a/hw/timer/aspeed_timer.c b/hw/timer/aspeed_timer.c new file mode 100644 index 0000000..0359528 --- /dev/null +++ b/hw/timer/aspeed_timer.c @@ -0,0 +1,313 @@ +/* + * ASPEED AST2400 Timer + * + * Andrew Jeffery + * + * Copyright (C) 2015, 2016 IBM Corp. + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include "hw/sysbus.h" +#include "qemu/timer.h" +#include "qemu-common.h" +#include "hw/ptimer.h" +#include "qemu/main-loop.h" +#include "hw/timer/aspeed_timer.h" +#include "trace.h" + +#define TIMER_NR_REGS 4 + +#define TIMER_CTRL_BITS 4 + +#define TIMER_CLOCK_USE_EXT true +#define TIMER_CLOCK_EXT_HZ 1000000 +#define TIMER_CLOCK_USE_APB false +#define TIMER_CLOCK_APB_HZ 24000000 + +#define TIMER_CTRL_OP_ENABLE 0 +#define TIMER_CTRL_OP_CLOCK_SELECT 1 +#define TIMER_CTRL_OP_OVERFLOW_INTERRUPT 2 +#define TIMER_CTRL_OP_PULSE_ENABLE 3 + +#define TIMER_REG_STATUS 0 +#define TIMER_REG_RELOAD 1 +#define TIMER_REG_MATCH_FIRST 2 +#define TIMER_REG_MATCH_SECOND 3 + +static inline bool timer_can_pulse(AspeedTimer *t) +{ + return t->id >= 4; +} + +static void aspeed_timer_irq_update(AspeedTimer *t) +{ + qemu_set_irq(t->irq, t->enabled); +} + +static void aspeed_timer_tick(void *opaque) +{ + AspeedTimer *t = opaque; + + aspeed_timer_irq_update(t); +} + +static uint64_t aspeed_timer_get_value(AspeedTimer *t, int reg) +{ + uint64_t value; + + switch (reg) { + case TIMER_REG_STATUS: + value = ptimer_get_count(t->timer); + break; + case TIMER_REG_RELOAD: + value = t->reload; + break; + case TIMER_REG_MATCH_FIRST: + case TIMER_REG_MATCH_SECOND: + value = t->match[reg - 2]; + break; + default: + qemu_log_mask(LOG_UNIMP, "Programming error: unexpected reg: %d\n", + reg); + value = 0; + break; + } + return value; +} + +static uint64_t aspeed_timer_read(void *opaque, hwaddr offset, unsigned size) +{ + AspeedTimerState *s = opaque; + const int reg = (offset & 0xf) / 4; + uint64_t value; + + switch (offset) { + case 0x30: /* Control Register */ + value = s->ctrl; + break; + case 0x34: /* Control Register 2 */ + value = s->ctrl2; + break; + case 0x00 ... 0x2c: /* Timers 1 - 4 */ + value = aspeed_timer_get_value(&s->timers[(offset >> 4)], reg); + break; + case 0x40 ... 0x8c: /* Timers 5 - 8 */ + value = aspeed_timer_get_value(&s->timers[(offset >> 4) - 1], reg); + break; + /* Illegal */ + case 0x38: + case 0x3C: + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, offset); + value = 0; + break; + } + trace_aspeed_timer_read(offset, size, value); + return value; +} + +static void aspeed_timer_set_value(AspeedTimerState *s, int timer, int reg, + uint32_t value) +{ + AspeedTimer *t; + + assert(timer >= 0 && timer < ASPEED_TIMER_NR_TIMERS && + "Programming error: Unexpected timer index"); + trace_aspeed_timer_set_value(timer, reg, value); + t = &s->timers[timer]; + switch (reg) { + case TIMER_REG_STATUS: + if (t->enabled) { + ptimer_set_count(t->timer, value); + } + break; + case TIMER_REG_RELOAD: + t->reload = value; + ptimer_set_limit(t->timer, value, 1); + break; + case TIMER_REG_MATCH_FIRST: + case TIMER_REG_MATCH_SECOND: + /* Nothing is done to make matching work, we just record the value */ + t->match[reg - 2] = value; + break; + default: + qemu_log_mask(LOG_UNIMP, "Programming error: unexpected reg: %d\n", + reg); + break; + } +} + +static void aspeed_timer_ctrl_op(AspeedTimer *t, int op, bool set) +{ + switch (op) { + case TIMER_CTRL_OP_ENABLE: + trace_aspeed_timer_ctrl_op_timer_enable(t->id, set); + if (set) { + ptimer_run(t->timer, 0); + } else { + ptimer_stop(t->timer); + ptimer_set_limit(t->timer, t->reload, 1); + } + t->enabled = set; + break; + case TIMER_CTRL_OP_CLOCK_SELECT: + trace_aspeed_timer_ctrl_op_clock_select(t->id, set); + if (set) { + ptimer_set_freq(t->timer, TIMER_CLOCK_EXT_HZ); + } else { + ptimer_set_freq(t->timer, TIMER_CLOCK_APB_HZ); + } + break; + case TIMER_CTRL_OP_OVERFLOW_INTERRUPT: + trace_aspeed_timer_ctrl_op_overflow_interrupt(t->id, set); + break; + case TIMER_CTRL_OP_PULSE_ENABLE: + if (timer_can_pulse(t)) { + trace_aspeed_timer_ctrl_op_pulse_enable(t->id, set); + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "Timer does not support pulse mode\n"); + } + break; + default: + qemu_log_mask(LOG_UNIMP, "Programming error, unexpected op: %d\n", + op); + break; + } +} + +static void aspeed_timer_set_ctrl(AspeedTimerState *s, uint32_t new) +{ + int i; + uint32_t changed = s->ctrl ^ new; + + while (32 > (i = ctz32(changed))) { + int timer, op; + bool set; + AspeedTimer *t; + + timer = i / TIMER_CTRL_BITS; + assert(timer < ASPEED_TIMER_NR_TIMERS); + t = &s->timers[timer]; + op = i % TIMER_CTRL_BITS; + set = new & (1U << i); + aspeed_timer_ctrl_op(t, op, set); + changed &= ~(1U << i); + } + s->ctrl = new; +} + +static void aspeed_timer_set_ctrl2(AspeedTimerState *s, uint32_t value) +{ + trace_aspeed_timer_set_ctrl2(value); +} + +static void aspeed_timer_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + const uint32_t tv = (uint32_t)(value & 0xFFFFFFFF); + const int reg = (offset & 0xf) / 4; + AspeedTimerState *s = opaque; + + switch (offset) { + /* Control Registers */ + case 0x30: + aspeed_timer_set_ctrl(s, tv); + break; + case 0x34: + aspeed_timer_set_ctrl2(s, tv); + break; + /* Timer Registers */ + case 0x00 ... 0x2c: + aspeed_timer_set_value(s, (offset >> TIMER_NR_REGS), reg, tv); + break; + case 0x40 ... 0x8c: + aspeed_timer_set_value(s, (offset >> TIMER_NR_REGS) - 1, reg, tv); + break; + /* Illegal */ + case 0x38: + case 0x3C: + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n", + __func__, offset); + break; + } +} + +static const MemoryRegionOps aspeed_timer_ops = { + .read = aspeed_timer_read, + .write = aspeed_timer_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, + .valid.unaligned = false, +}; + +static void aspeed_timer_init(AspeedTimer *t, uint8_t id, bool ext_clock) +{ + QEMUBH *bh; + + t->id = id; + bh = qemu_bh_new(aspeed_timer_tick, t); + assert(bh); + t->timer = ptimer_init(bh); + assert(t->timer); + aspeed_timer_ctrl_op(t, TIMER_CTRL_OP_CLOCK_SELECT, ext_clock); +} + +static void aspeed_timers_realize(DeviceState *dev, Error **errp) +{ + int i; + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + AspeedTimerState *s = ASPEED_TIMER(dev); + uint32_t clock_mask = 0; + + for (i = 0; i < ASPEED_TIMER_NR_TIMERS; i++) { + aspeed_timer_init(&s->timers[i], i, TIMER_CLOCK_USE_APB); + clock_mask |= (1 << (1 + i * TIMER_CTRL_BITS)); + sysbus_init_irq(sbd, &s->timers[i].irq); + } + /* Ensure control reg has timers configured with APB clock selected */ + s->ctrl &= ~clock_mask; + memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_timer_ops, s, + TYPE_ASPEED_TIMER, 0x1000); + sysbus_init_mmio(sbd, &s->iomem); +} + +static void timer_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = aspeed_timers_realize; + dc->desc = "ASPEED Timer"; +} + +static const TypeInfo aspeed_timer_info = { + .name = TYPE_ASPEED_TIMER, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AspeedTimerState), + .class_init = timer_class_init, +}; + +static void aspeed_timer_register_types(void) +{ + type_register_static(&aspeed_timer_info); +} + +type_init(aspeed_timer_register_types); diff --git a/include/hw/timer/aspeed_timer.h b/include/hw/timer/aspeed_timer.h new file mode 100644 index 0000000..a14db74 --- /dev/null +++ b/include/hw/timer/aspeed_timer.h @@ -0,0 +1,55 @@ +/* + * ASPEED AST2400 Timer + * + * Andrew Jeffery + * + * Copyright (C) 2016 IBM Corp. + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#ifndef ASPEED_TIMER_H +#define ASPEED_TIMER_H + +#include "hw/ptimer.h" + +#define ASPEED_TIMER(obj) \ + OBJECT_CHECK(AspeedTimerState, (obj), TYPE_ASPEED_TIMER); +#define TYPE_ASPEED_TIMER "aspeed.timer" +#define ASPEED_TIMER_NR_TIMERS 8 + +typedef struct AspeedTimer { + uint8_t id; + ptimer_state *timer; + uint32_t reload; + uint32_t match[2]; + qemu_irq irq; + /* ptimer doesn't expose any method to check whether it's enabled, which we + * require for updating the timer IRQ state */ + bool enabled; +} AspeedTimer; + +typedef struct AspeedTimerState { + /* < private > */ + SysBusDevice parent; + + /* < public > */ + MemoryRegion iomem; + AspeedTimer timers[ASPEED_TIMER_NR_TIMERS]; + uint32_t ctrl; + uint32_t ctrl2; + qemu_irq irq; +} AspeedTimerState; + +#endif /* ASPEED_TIMER_H */ diff --git a/trace-events b/trace-events index f986c81..5325a23 100644 --- a/trace-events +++ b/trace-events @@ -1890,3 +1890,12 @@ qio_channel_command_new_pid(void *ioc, int writefd, int readfd, int pid) "Comman qio_channel_command_new_spawn(void *ioc, const char *binary, int flags) "Command new spawn ioc=%p binary=%s flags=%d" qio_channel_command_abort(void *ioc, int pid) "Command abort ioc=%p pid=%d" qio_channel_command_wait(void *ioc, int pid, int ret, int status) "Command abort ioc=%p pid=%d ret=%d status=%d" + +# hw/timer/aspeed_timer.c +aspeed_timer_ctrl_op_timer_enable(uint8_t i, bool enable) "Configure timer %" PRIu8 ": %d" +aspeed_timer_ctrl_op_clock_select(uint8_t i, bool enable) "Use external clock on %" PRIu8 ": %d" +aspeed_timer_ctrl_op_pulse_enable(uint8_t i, bool enable) "Configure pulse mode on %" PRIu8 ": %d" +aspeed_timer_ctrl_op_overflow_interrupt(uint8_t i, bool enable) "Overflow interrupt on %" PRIu8 ": %d" +aspeed_timer_set_ctrl2(uint32_t value) "CTRL2 set to 0x%" PRIx32 +aspeed_timer_set_value(int timer, int reg, uint32_t value) "Write to register %d of timer %d: 0x%" PRIx32 +aspeed_timer_read(uint64_t offset, unsigned size, uint64_t value) "Read at 0x%" PRIx64 ": of size %u 0x%" PRIx64