| /* |
| * IMX GPT Timer |
| * |
| * Copyright (c) 2008 OK Labs |
| * Copyright (c) 2011 NICTA Pty Ltd |
| * Originally written by Hans Jiang |
| * Updated by Peter Chubb |
| * Updated by Jean-Christophe Dubois <jcd@tribudubois.net> |
| * |
| * This code is licensed under GPL version 2 or later. See |
| * the COPYING file in the top-level directory. |
| * |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "hw/irq.h" |
| #include "hw/timer/imx_gpt.h" |
| #include "migration/vmstate.h" |
| #include "qemu/module.h" |
| #include "qemu/log.h" |
| #include "trace.h" |
| |
| #ifndef DEBUG_IMX_GPT |
| #define DEBUG_IMX_GPT 0 |
| #endif |
| |
| static const char *imx_gpt_reg_name(uint32_t reg) |
| { |
| switch (reg) { |
| case 0: |
| return "CR"; |
| case 1: |
| return "PR"; |
| case 2: |
| return "SR"; |
| case 3: |
| return "IR"; |
| case 4: |
| return "OCR1"; |
| case 5: |
| return "OCR2"; |
| case 6: |
| return "OCR3"; |
| case 7: |
| return "ICR1"; |
| case 8: |
| return "ICR2"; |
| case 9: |
| return "CNT"; |
| default: |
| return "[?]"; |
| } |
| } |
| |
| static const VMStateDescription vmstate_imx_timer_gpt = { |
| .name = TYPE_IMX_GPT, |
| .version_id = 3, |
| .minimum_version_id = 3, |
| .fields = (const VMStateField[]) { |
| VMSTATE_UINT32(cr, IMXGPTState), |
| VMSTATE_UINT32(pr, IMXGPTState), |
| VMSTATE_UINT32(sr, IMXGPTState), |
| VMSTATE_UINT32(ir, IMXGPTState), |
| VMSTATE_UINT32(ocr1, IMXGPTState), |
| VMSTATE_UINT32(ocr2, IMXGPTState), |
| VMSTATE_UINT32(ocr3, IMXGPTState), |
| VMSTATE_UINT32(icr1, IMXGPTState), |
| VMSTATE_UINT32(icr2, IMXGPTState), |
| VMSTATE_UINT32(cnt, IMXGPTState), |
| VMSTATE_UINT32(next_timeout, IMXGPTState), |
| VMSTATE_UINT32(next_int, IMXGPTState), |
| VMSTATE_UINT32(freq, IMXGPTState), |
| VMSTATE_PTIMER(timer, IMXGPTState), |
| VMSTATE_END_OF_LIST() |
| } |
| }; |
| |
| static const IMXClk imx25_gpt_clocks[] = { |
| CLK_NONE, /* 000 No clock source */ |
| CLK_IPG, /* 001 ipg_clk, 532MHz*/ |
| CLK_IPG_HIGH, /* 010 ipg_clk_highfreq */ |
| CLK_NONE, /* 011 not defined */ |
| CLK_32k, /* 100 ipg_clk_32k */ |
| CLK_32k, /* 101 ipg_clk_32k */ |
| CLK_32k, /* 110 ipg_clk_32k */ |
| CLK_32k, /* 111 ipg_clk_32k */ |
| }; |
| |
| static const IMXClk imx31_gpt_clocks[] = { |
| CLK_NONE, /* 000 No clock source */ |
| CLK_IPG, /* 001 ipg_clk, 532MHz*/ |
| CLK_IPG_HIGH, /* 010 ipg_clk_highfreq */ |
| CLK_NONE, /* 011 not defined */ |
| CLK_32k, /* 100 ipg_clk_32k */ |
| CLK_NONE, /* 101 not defined */ |
| CLK_NONE, /* 110 not defined */ |
| CLK_NONE, /* 111 not defined */ |
| }; |
| |
| static const IMXClk imx6_gpt_clocks[] = { |
| CLK_NONE, /* 000 No clock source */ |
| CLK_IPG, /* 001 ipg_clk, 532MHz*/ |
| CLK_IPG_HIGH, /* 010 ipg_clk_highfreq */ |
| CLK_EXT, /* 011 External clock */ |
| CLK_32k, /* 100 ipg_clk_32k */ |
| CLK_HIGH_DIV, /* 101 reference clock / 8 */ |
| CLK_NONE, /* 110 not defined */ |
| CLK_HIGH, /* 111 reference clock */ |
| }; |
| |
| static const IMXClk imx6ul_gpt_clocks[] = { |
| CLK_NONE, /* 000 No clock source */ |
| CLK_IPG, /* 001 ipg_clk, 532MHz*/ |
| CLK_IPG_HIGH, /* 010 ipg_clk_highfreq */ |
| CLK_EXT, /* 011 External clock */ |
| CLK_32k, /* 100 ipg_clk_32k */ |
| CLK_NONE, /* 101 not defined */ |
| CLK_NONE, /* 110 not defined */ |
| CLK_NONE, /* 111 not defined */ |
| }; |
| |
| static const IMXClk imx7_gpt_clocks[] = { |
| CLK_NONE, /* 000 No clock source */ |
| CLK_IPG, /* 001 ipg_clk, 532MHz*/ |
| CLK_IPG_HIGH, /* 010 ipg_clk_highfreq */ |
| CLK_EXT, /* 011 External clock */ |
| CLK_32k, /* 100 ipg_clk_32k */ |
| CLK_HIGH, /* 101 reference clock */ |
| CLK_NONE, /* 110 not defined */ |
| CLK_NONE, /* 111 not defined */ |
| }; |
| |
| /* Must be called from within ptimer_transaction_begin/commit block */ |
| static void imx_gpt_set_freq(IMXGPTState *s) |
| { |
| uint32_t clksrc = extract32(s->cr, GPT_CR_CLKSRC_SHIFT, 3); |
| |
| s->freq = imx_ccm_get_clock_frequency(s->ccm, |
| s->clocks[clksrc]) / (1 + s->pr); |
| |
| trace_imx_gpt_set_freq(clksrc, s->freq); |
| |
| if (s->freq) { |
| ptimer_set_freq(s->timer, s->freq); |
| } |
| } |
| |
| static void imx_gpt_update_int(IMXGPTState *s) |
| { |
| if ((s->sr & s->ir) && (s->cr & GPT_CR_EN)) { |
| qemu_irq_raise(s->irq); |
| } else { |
| qemu_irq_lower(s->irq); |
| } |
| } |
| |
| static uint32_t imx_gpt_update_count(IMXGPTState *s) |
| { |
| s->cnt = s->next_timeout - (uint32_t)ptimer_get_count(s->timer); |
| |
| return s->cnt; |
| } |
| |
| static inline uint32_t imx_gpt_find_limit(uint32_t count, uint32_t reg, |
| uint32_t timeout) |
| { |
| if ((count < reg) && (timeout > reg)) { |
| timeout = reg; |
| } |
| |
| return timeout; |
| } |
| |
| /* Must be called from within ptimer_transaction_begin/commit block */ |
| static void imx_gpt_compute_next_timeout(IMXGPTState *s, bool event) |
| { |
| uint32_t timeout = GPT_TIMER_MAX; |
| uint32_t count; |
| long long limit; |
| |
| if (!(s->cr & GPT_CR_EN)) { |
| /* if not enabled just return */ |
| return; |
| } |
| |
| /* update the count */ |
| count = imx_gpt_update_count(s); |
| |
| if (event) { |
| /* |
| * This is an event (the ptimer reached 0 and stopped), and the |
| * timer counter is now equal to s->next_timeout. |
| */ |
| if (!(s->cr & GPT_CR_FRR) && (count == s->ocr1)) { |
| /* We are in restart mode and we crossed the compare channel 1 |
| * value. We need to reset the counter to 0. |
| */ |
| count = s->cnt = s->next_timeout = 0; |
| } else if (count == GPT_TIMER_MAX) { |
| /* We reached GPT_TIMER_MAX so we need to rollover */ |
| count = s->cnt = s->next_timeout = 0; |
| } |
| } |
| |
| /* now, find the next timeout related to count */ |
| |
| if (s->ir & GPT_IR_OF1IE) { |
| timeout = imx_gpt_find_limit(count, s->ocr1, timeout); |
| } |
| if (s->ir & GPT_IR_OF2IE) { |
| timeout = imx_gpt_find_limit(count, s->ocr2, timeout); |
| } |
| if (s->ir & GPT_IR_OF3IE) { |
| timeout = imx_gpt_find_limit(count, s->ocr3, timeout); |
| } |
| |
| /* find the next set of interrupts to raise for next timer event */ |
| |
| s->next_int = 0; |
| if ((s->ir & GPT_IR_OF1IE) && (timeout == s->ocr1)) { |
| s->next_int |= GPT_SR_OF1; |
| } |
| if ((s->ir & GPT_IR_OF2IE) && (timeout == s->ocr2)) { |
| s->next_int |= GPT_SR_OF2; |
| } |
| if ((s->ir & GPT_IR_OF3IE) && (timeout == s->ocr3)) { |
| s->next_int |= GPT_SR_OF3; |
| } |
| if ((s->ir & GPT_IR_ROVIE) && (timeout == GPT_TIMER_MAX)) { |
| s->next_int |= GPT_SR_ROV; |
| } |
| |
| /* the new range to count down from */ |
| limit = timeout - imx_gpt_update_count(s); |
| |
| if (limit < 0) { |
| /* |
| * if we reach here, then QEMU is running too slow and we pass the |
| * timeout limit while computing it. Let's deliver the interrupt |
| * and compute a new limit. |
| */ |
| s->sr |= s->next_int; |
| |
| imx_gpt_compute_next_timeout(s, event); |
| |
| imx_gpt_update_int(s); |
| } else { |
| /* New timeout value */ |
| s->next_timeout = timeout; |
| |
| /* reset the limit to the computed range */ |
| ptimer_set_limit(s->timer, limit, 1); |
| } |
| } |
| |
| static uint64_t imx_gpt_read(void *opaque, hwaddr offset, unsigned size) |
| { |
| IMXGPTState *s = IMX_GPT(opaque); |
| uint32_t reg_value = 0; |
| |
| switch (offset >> 2) { |
| case 0: /* Control Register */ |
| reg_value = s->cr; |
| break; |
| |
| case 1: /* prescaler */ |
| reg_value = s->pr; |
| break; |
| |
| case 2: /* Status Register */ |
| reg_value = s->sr; |
| break; |
| |
| case 3: /* Interrupt Register */ |
| reg_value = s->ir; |
| break; |
| |
| case 4: /* Output Compare Register 1 */ |
| reg_value = s->ocr1; |
| break; |
| |
| case 5: /* Output Compare Register 2 */ |
| reg_value = s->ocr2; |
| break; |
| |
| case 6: /* Output Compare Register 3 */ |
| reg_value = s->ocr3; |
| break; |
| |
| case 7: /* input Capture Register 1 */ |
| qemu_log_mask(LOG_UNIMP, "[%s]%s: icr1 feature is not implemented\n", |
| TYPE_IMX_GPT, __func__); |
| reg_value = s->icr1; |
| break; |
| |
| case 8: /* input Capture Register 2 */ |
| qemu_log_mask(LOG_UNIMP, "[%s]%s: icr2 feature is not implemented\n", |
| TYPE_IMX_GPT, __func__); |
| reg_value = s->icr2; |
| break; |
| |
| case 9: /* cnt */ |
| imx_gpt_update_count(s); |
| reg_value = s->cnt; |
| break; |
| |
| default: |
| qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%" |
| HWADDR_PRIx "\n", TYPE_IMX_GPT, __func__, offset); |
| break; |
| } |
| |
| trace_imx_gpt_read(imx_gpt_reg_name(offset >> 2), reg_value); |
| |
| return reg_value; |
| } |
| |
| |
| static void imx_gpt_reset_common(IMXGPTState *s, bool is_soft_reset) |
| { |
| ptimer_transaction_begin(s->timer); |
| /* stop timer */ |
| ptimer_stop(s->timer); |
| |
| /* Soft reset and hard reset differ only in their handling of the CR |
| * register -- soft reset preserves the values of some bits there. |
| */ |
| if (is_soft_reset) { |
| /* Clear all CR bits except those that are preserved by soft reset. */ |
| s->cr &= GPT_CR_EN | GPT_CR_ENMOD | GPT_CR_STOPEN | GPT_CR_DOZEN | |
| GPT_CR_WAITEN | GPT_CR_DBGEN | |
| (GPT_CR_CLKSRC_MASK << GPT_CR_CLKSRC_SHIFT); |
| } else { |
| s->cr = 0; |
| } |
| s->sr = 0; |
| s->pr = 0; |
| s->ir = 0; |
| s->cnt = 0; |
| s->ocr1 = GPT_TIMER_MAX; |
| s->ocr2 = GPT_TIMER_MAX; |
| s->ocr3 = GPT_TIMER_MAX; |
| s->icr1 = 0; |
| s->icr2 = 0; |
| |
| s->next_timeout = GPT_TIMER_MAX; |
| s->next_int = 0; |
| |
| /* compute new freq */ |
| imx_gpt_set_freq(s); |
| |
| /* reset the limit to GPT_TIMER_MAX */ |
| ptimer_set_limit(s->timer, GPT_TIMER_MAX, 1); |
| |
| /* if the timer is still enabled, restart it */ |
| if (s->freq && (s->cr & GPT_CR_EN)) { |
| ptimer_run(s->timer, 1); |
| } |
| ptimer_transaction_commit(s->timer); |
| } |
| |
| static void imx_gpt_soft_reset(DeviceState *dev) |
| { |
| IMXGPTState *s = IMX_GPT(dev); |
| imx_gpt_reset_common(s, true); |
| } |
| |
| static void imx_gpt_reset(DeviceState *dev) |
| { |
| IMXGPTState *s = IMX_GPT(dev); |
| imx_gpt_reset_common(s, false); |
| } |
| |
| static void imx_gpt_write(void *opaque, hwaddr offset, uint64_t value, |
| unsigned size) |
| { |
| IMXGPTState *s = IMX_GPT(opaque); |
| uint32_t oldreg; |
| |
| trace_imx_gpt_write(imx_gpt_reg_name(offset >> 2), (uint32_t)value); |
| |
| switch (offset >> 2) { |
| case 0: |
| oldreg = s->cr; |
| s->cr = value & ~0x7c14; |
| if (s->cr & GPT_CR_SWR) { /* force reset */ |
| /* handle the reset */ |
| imx_gpt_soft_reset(DEVICE(s)); |
| } else { |
| /* set our freq, as the source might have changed */ |
| ptimer_transaction_begin(s->timer); |
| imx_gpt_set_freq(s); |
| |
| if ((oldreg ^ s->cr) & GPT_CR_EN) { |
| if (s->cr & GPT_CR_EN) { |
| if (s->cr & GPT_CR_ENMOD) { |
| s->next_timeout = GPT_TIMER_MAX; |
| ptimer_set_count(s->timer, GPT_TIMER_MAX); |
| imx_gpt_compute_next_timeout(s, false); |
| } |
| ptimer_run(s->timer, 1); |
| } else { |
| /* stop timer */ |
| ptimer_stop(s->timer); |
| } |
| } |
| ptimer_transaction_commit(s->timer); |
| } |
| break; |
| |
| case 1: /* Prescaler */ |
| s->pr = value & 0xfff; |
| ptimer_transaction_begin(s->timer); |
| imx_gpt_set_freq(s); |
| ptimer_transaction_commit(s->timer); |
| break; |
| |
| case 2: /* SR */ |
| s->sr &= ~(value & 0x3f); |
| imx_gpt_update_int(s); |
| break; |
| |
| case 3: /* IR -- interrupt register */ |
| s->ir = value & 0x3f; |
| imx_gpt_update_int(s); |
| |
| ptimer_transaction_begin(s->timer); |
| imx_gpt_compute_next_timeout(s, false); |
| ptimer_transaction_commit(s->timer); |
| |
| break; |
| |
| case 4: /* OCR1 -- output compare register */ |
| s->ocr1 = value; |
| |
| ptimer_transaction_begin(s->timer); |
| /* In non-freerun mode, reset count when this register is written */ |
| if (!(s->cr & GPT_CR_FRR)) { |
| s->next_timeout = GPT_TIMER_MAX; |
| ptimer_set_limit(s->timer, GPT_TIMER_MAX, 1); |
| } |
| |
| /* compute the new timeout */ |
| imx_gpt_compute_next_timeout(s, false); |
| ptimer_transaction_commit(s->timer); |
| |
| break; |
| |
| case 5: /* OCR2 -- output compare register */ |
| s->ocr2 = value; |
| |
| /* compute the new timeout */ |
| ptimer_transaction_begin(s->timer); |
| imx_gpt_compute_next_timeout(s, false); |
| ptimer_transaction_commit(s->timer); |
| |
| break; |
| |
| case 6: /* OCR3 -- output compare register */ |
| s->ocr3 = value; |
| |
| /* compute the new timeout */ |
| ptimer_transaction_begin(s->timer); |
| imx_gpt_compute_next_timeout(s, false); |
| ptimer_transaction_commit(s->timer); |
| |
| break; |
| |
| default: |
| qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%" |
| HWADDR_PRIx "\n", TYPE_IMX_GPT, __func__, offset); |
| break; |
| } |
| } |
| |
| static void imx_gpt_timeout(void *opaque) |
| { |
| IMXGPTState *s = IMX_GPT(opaque); |
| |
| trace_imx_gpt_timeout(); |
| |
| s->sr |= s->next_int; |
| s->next_int = 0; |
| |
| imx_gpt_compute_next_timeout(s, true); |
| |
| imx_gpt_update_int(s); |
| |
| if (s->freq && (s->cr & GPT_CR_EN)) { |
| ptimer_run(s->timer, 1); |
| } |
| } |
| |
| static const MemoryRegionOps imx_gpt_ops = { |
| .read = imx_gpt_read, |
| .write = imx_gpt_write, |
| .endianness = DEVICE_NATIVE_ENDIAN, |
| }; |
| |
| |
| static void imx_gpt_realize(DeviceState *dev, Error **errp) |
| { |
| IMXGPTState *s = IMX_GPT(dev); |
| SysBusDevice *sbd = SYS_BUS_DEVICE(dev); |
| |
| sysbus_init_irq(sbd, &s->irq); |
| memory_region_init_io(&s->iomem, OBJECT(s), &imx_gpt_ops, s, TYPE_IMX_GPT, |
| 0x00001000); |
| sysbus_init_mmio(sbd, &s->iomem); |
| |
| s->timer = ptimer_init(imx_gpt_timeout, s, PTIMER_POLICY_LEGACY); |
| } |
| |
| static void imx_gpt_class_init(ObjectClass *klass, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| |
| dc->realize = imx_gpt_realize; |
| device_class_set_legacy_reset(dc, imx_gpt_reset); |
| dc->vmsd = &vmstate_imx_timer_gpt; |
| dc->desc = "i.MX general timer"; |
| } |
| |
| static void imx25_gpt_init(Object *obj) |
| { |
| IMXGPTState *s = IMX_GPT(obj); |
| |
| s->clocks = imx25_gpt_clocks; |
| } |
| |
| static void imx31_gpt_init(Object *obj) |
| { |
| IMXGPTState *s = IMX_GPT(obj); |
| |
| s->clocks = imx31_gpt_clocks; |
| } |
| |
| static void imx6_gpt_init(Object *obj) |
| { |
| IMXGPTState *s = IMX_GPT(obj); |
| |
| s->clocks = imx6_gpt_clocks; |
| } |
| |
| static void imx6ul_gpt_init(Object *obj) |
| { |
| IMXGPTState *s = IMX_GPT(obj); |
| |
| s->clocks = imx6ul_gpt_clocks; |
| } |
| |
| static void imx7_gpt_init(Object *obj) |
| { |
| IMXGPTState *s = IMX_GPT(obj); |
| |
| s->clocks = imx7_gpt_clocks; |
| } |
| |
| static const TypeInfo imx25_gpt_info = { |
| .name = TYPE_IMX25_GPT, |
| .parent = TYPE_SYS_BUS_DEVICE, |
| .instance_size = sizeof(IMXGPTState), |
| .instance_init = imx25_gpt_init, |
| .class_init = imx_gpt_class_init, |
| }; |
| |
| static const TypeInfo imx31_gpt_info = { |
| .name = TYPE_IMX31_GPT, |
| .parent = TYPE_IMX25_GPT, |
| .instance_init = imx31_gpt_init, |
| }; |
| |
| static const TypeInfo imx6_gpt_info = { |
| .name = TYPE_IMX6_GPT, |
| .parent = TYPE_IMX25_GPT, |
| .instance_init = imx6_gpt_init, |
| }; |
| |
| static const TypeInfo imx6ul_gpt_info = { |
| .name = TYPE_IMX6UL_GPT, |
| .parent = TYPE_IMX25_GPT, |
| .instance_init = imx6ul_gpt_init, |
| }; |
| |
| static const TypeInfo imx7_gpt_info = { |
| .name = TYPE_IMX7_GPT, |
| .parent = TYPE_IMX25_GPT, |
| .instance_init = imx7_gpt_init, |
| }; |
| |
| static void imx_gpt_register_types(void) |
| { |
| type_register_static(&imx25_gpt_info); |
| type_register_static(&imx31_gpt_info); |
| type_register_static(&imx6_gpt_info); |
| type_register_static(&imx6ul_gpt_info); |
| type_register_static(&imx7_gpt_info); |
| } |
| |
| type_init(imx_gpt_register_types) |