On 12 December 2011 06:43, Evgeny Voevodin <e.voevo...@samsung.com> wrote: > > Signed-off-by: Evgeny Voevodin <e.voevo...@samsung.com> > --- > Makefile.target | 2 +- > hw/exynos4210.c | 12 ++ > hw/exynos4210_pwm.c | 433 > +++++++++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 446 insertions(+), 1 deletions(-) > create mode 100644 hw/exynos4210_pwm.c > > diff --git a/Makefile.target b/Makefile.target > index 779c9d4..709e9e2 100644 > --- a/Makefile.target > +++ b/Makefile.target > @@ -345,7 +345,7 @@ obj-arm-y += arm_boot.o pl011.o pl031.o pl050.o pl080.o > pl110.o pl181.o pl190.o > obj-arm-y += versatile_pci.o > obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o > obj-arm-y += exynos4210.o exynos4210_cmu.o exynos4210_uart.o > exynos4210_gic.o \ > - exynos4210_combiner.o > + exynos4210_combiner.o exynos4210_pwm.o > obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o > obj-arm-y += pl061.o > obj-arm-y += arm-semi.o > diff --git a/hw/exynos4210.c b/hw/exynos4210.c > index 45d427e..7a7760d 100644 > --- a/hw/exynos4210.c > +++ b/hw/exynos4210.c > @@ -65,6 +65,9 @@ > /* SFR Base Address for CMUs */ > #define EXYNOS4210_CMU_BASE_ADDR 0x10030000 > > +/* PWM */ > +#define EXYNOS4210_PWM_BASE_ADDR 0x139D0000 > + > /* UART's definitions */ > #define EXYNOS4210_UART_BASE_ADDR 0x13800000 > #define EXYNOS4210_UART_SHIFT 0x00010000 > @@ -364,6 +367,15 @@ static void exynos4210_init(ram_addr_t ram_size, > /* CMU */ > sysbus_create_simple("exynos4210.cmu", EXYNOS4210_CMU_BASE_ADDR, NULL); > > + /* PWM */ > + sysbus_create_varargs("exynos4210.pwm", EXYNOS4210_PWM_BASE_ADDR, > + irq_table[exynos4210_get_irq(22, 0)], > + irq_table[exynos4210_get_irq(22, 1)], > + irq_table[exynos4210_get_irq(22, 2)], > + irq_table[exynos4210_get_irq(22, 3)], > + irq_table[exynos4210_get_irq(22, 4)], > + NULL); > + > /*** UARTs ***/ > for (n = 0; n < EXYNOS4210_UARTS_NUMBER; n++) { > > diff --git a/hw/exynos4210_pwm.c b/hw/exynos4210_pwm.c > new file mode 100644 > index 0000000..1e80f10 > --- /dev/null > +++ b/hw/exynos4210_pwm.c > @@ -0,0 +1,433 @@ > +/* > + * Samsung exynos4210 Pulse Width Modulation Timer > + * > + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. > + * All rights reserved. > + * > + * Evgeny Voevodin <e.voevo...@samsung.com> > + * > + * 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 "sysbus.h" > +#include "qemu-timer.h" > +#include "qemu-common.h" > +#include "hw.h" > + > +#include "exynos4210.h" > + > +//#define DEBUG_PWM > + > +#ifdef DEBUG_PWM > +#define DPRINTF(fmt, ...) \ > + do { fprintf(stdout, "PWM: [%24s:%5d] " fmt, __func__, __LINE__, \ > + ## __VA_ARGS__); } while (0) > +#else > +#define DPRINTF(fmt, ...) do {} while (0) > +#endif > + > +#define EXYNOS4210_PWM_TIMERS_NUM 5 > +#define EXYNOS4210_PWM_REG_MEM_SIZE 0x50 > + > +#define TCFG0 0x0000 > +#define TCFG1 0x0004 > +#define TCON 0x0008 > +#define TCNTB0 0x000C > +#define TCMPB0 0x0010 > +#define TCNTO0 0x0014 > +#define TCNTB1 0x0018 > +#define TCMPB1 0x001C > +#define TCNTO1 0x0020 > +#define TCNTB2 0x0024 > +#define TCMPB2 0x0028 > +#define TCNTO2 0x002C > +#define TCNTB3 0x0030 > +#define TCMPB3 0x0034 > +#define TCNTO3 0x0038 > +#define TCNTB4 0x003C > +#define TCNTO4 0x0040 > +#define TINT_CSTAT 0x0044 > + > +#define TCNTB(x) (0xC*x) > +#define TCMPB(x) (0xC*x+1) > +#define TCNTO(x) (0xC*x+2) > + > +#define GET_PRESCALER(reg, x) ((reg&(0xFF<<(8*x)))>>8*x) > +#define GET_DIVIDER(reg, x) (1<<((0xF<<(4*x))>>(4*x))) > + > +/* > + * Attention! Timer4 doesn't have OUTPUT_INVERTER, > + * so Auto Reload bit is not accessible by macros! > + */ > +#define TCON_TIMER_BASE(x) ((x ? 1 : 0)*4 + 4*x) > +#define TCON_TIMER_START(x) (1<<(TCON_TIMER_BASE(x) + 0)) > +#define TCON_TIMER_MANUAL_UPD(x) (1<<(TCON_TIMER_BASE(x) + 1)) > +#define TCON_TIMER_OUTPUT_INV(x) (1<<(TCON_TIMER_BASE(x) + 2)) > +#define TCON_TIMER_AUTO_RELOAD(x) (1<<(TCON_TIMER_BASE(x) + 3)) > +#define TCON_TIMER4_AUTO_RELOAD (1<<22) > + > +#define TINT_CSTAT_STATUS(x) (1<<(5+x)) > +#define TINT_CSTAT_ENABLE(x) (1<<x) > + > +/* timer struct */ > +typedef struct { > + uint32_t id; /* timer id */ > + qemu_irq irq; /* local timer irq */ > + uint32_t freq; /* timer frequency */ > + > + /* use ptimer.c to represent count down timer */ > + ptimer_state *ptimer; /* timer */ > + > + /* registers */ > + uint32_t reg_tcntb; /* counter register buffer */ > + uint32_t reg_tcmpb; /* compare register buffer */ > + > +} Exynos4210_pwm; > + > + > +typedef struct Exynos4210PWMState { > + SysBusDevice busdev; > + MemoryRegion iomem; > + > + uint32_t reg_tcfg[2]; > + uint32_t reg_tcon; > + uint32_t reg_tint_cstat; > + > + Exynos4210CmuClock clk; /* clock source for timer */ > + Exynos4210_pwm timer[EXYNOS4210_PWM_TIMERS_NUM]; > + > +} Exynos4210PWMState; > + > +/*** VMState ***/ > +static const VMStateDescription VMState_Exynos4210_pwm = { > + .name = "exynos4210.pwm.pwm", > + .version_id = 1, > + .minimum_version_id = 1, > + .minimum_version_id_old = 1, > + .fields = (VMStateField[]) { > + VMSTATE_UINT32(id, Exynos4210_pwm), > + VMSTATE_UINT32(freq, Exynos4210_pwm), > + VMSTATE_UINT32(reg_tcntb, Exynos4210_pwm), > + VMSTATE_UINT32(reg_tcmpb, Exynos4210_pwm), > + VMSTATE_END_OF_LIST() > + } > +}; > + > +static const VMStateDescription VMState_Exynos4210PWMState = { > + .name = "exynos4210.pwm", > + .version_id = 1, > + .minimum_version_id = 1, > + .minimum_version_id_old = 1, > + .fields = (VMStateField[]) { > + VMSTATE_UINT32_ARRAY(reg_tcfg, Exynos4210PWMState, 2), > + VMSTATE_UINT32(reg_tcon, Exynos4210PWMState), > + VMSTATE_UINT32(reg_tint_cstat, Exynos4210PWMState), > + VMSTATE_STRUCT_ARRAY(timer, Exynos4210PWMState, > + EXYNOS4210_PWM_TIMERS_NUM, 0, > + VMState_Exynos4210_pwm, Exynos4210_pwm), > + VMSTATE_END_OF_LIST() > + } > +}; > + > +/* > + * PWM update frequency > + */ > +static void exynos4210_pwm_update_freq(Exynos4210PWMState *s, uint32_t id) > +{ > + uint32_t freq; > + freq = s->timer[id].freq; > + if (id > 1) { > + s->timer[id].freq = exynos4210_cmu_get_rate(s->clk) / > + ((GET_PRESCALER(s->reg_tcfg[0], 1) + 1) * > + (1<<GET_DIVIDER(s->reg_tcfg[1], id))); > + } else { > + s->timer[id].freq = exynos4210_cmu_get_rate(s->clk) / > + ((GET_PRESCALER(s->reg_tcfg[0], 0) + 1) * > + (1<<GET_DIVIDER(s->preg_tcfg[1], id))); > + } > + > + if (freq != s->timer[id].freq) { > + ptimer_set_freq(s->timer[id].ptimer, s->timer[id].freq); > + DPRINTF("freq=%dHz\n", s->timer[id].freq); > + } > +} > + > +/* > + * Counter tick handler > + */ > +static void exynos4210_pwm_tick(void *opaque, uint32_t id) > +{ > + Exynos4210PWMState *s = (Exynos4210PWMState *)opaque; > + bool cmp; > + > + DPRINTF("timer %d tick\n", id); > + > + /* set irq status */ > + s->reg_tint_cstat |= TINT_CSTAT_STATUS(id); > + > + /* raise IRQ */ > + if (s->reg_tint_cstat & TINT_CSTAT_ENABLE(id)) { > + DPRINTF("timer %d IRQ\n", id); > + qemu_irq_raise(s->timer[id].irq); > + } > + > + /* reload timer */ > + if (id != 4) { > + cmp = s->reg_tcon & TCON_TIMER_AUTO_RELOAD(id); > + } else { > + cmp = s->reg_tcon & TCON_TIMER4_AUTO_RELOAD; > + } > + > + if (cmp) { > + DPRINTF("auto reload timer %d count to %x\n", id, > + s->timer[id].reg_tcntb); > + ptimer_set_count(s->timer[id].ptimer, s->timer[id].reg_tcntb); > + ptimer_run(s->timer[id].ptimer, 1); > + } else { > + /* stop timer, set status to STOP, see Basic Timer Operation */ > + s->reg_tcon = ~TCON_TIMER_START(id); > + ptimer_stop(s->timer[id].ptimer); > + } > +} > + > +static void exynos4210_pwm_tick0(void *opaque) > +{ > + exynos4210_pwm_tick(opaque, 0); > +} > +static void exynos4210_pwm_tick1(void *opaque) > +{ > + exynos4210_pwm_tick(opaque, 1); > +} > +static void exynos4210_pwm_tick2(void *opaque) > +{ > + exynos4210_pwm_tick(opaque, 2); > +} > +static void exynos4210_pwm_tick3(void *opaque) > +{ > + exynos4210_pwm_tick(opaque, 3); > +} > +static void exynos4210_pwm_tick4(void *opaque) > +{ > + exynos4210_pwm_tick(opaque, 4); > +}
If you make the Exynos4210_pwm struct include a pointer back to the Exynos4210PWMState struct, then you can make exynos4210_pwm_tick take a pointer to the Exynos4210_pwm struct, and then you can drop these wrapper functions and just say bh[i] = qemu_bh_new(exynos4210_pwm_tick, &s->timer[i]); > + > +/* > + * PWM Read > + */ > +static uint64_t exynos4210_pwm_read(void *opaque, target_phys_addr_t offset, > + unsigned size) > +{ > + Exynos4210PWMState *s = (Exynos4210PWMState *)opaque; > + uint32_t value = 0; > + int index; > + > + switch (offset) { > + case TCFG0: case TCFG1: > + index = (offset - TCFG0)>>2; > + value = s->reg_tcfg[index]; > + break; > + > + case TCON: > + value = s->reg_tcon; > + break; > + > + case TCNTB0: case TCNTB1: > + case TCNTB2: case TCNTB3: case TCNTB4: > + index = (offset - TCNTB0)/0xC; > + value = s->timer[index].reg_tcntb; > + break; > + > + case TCMPB0: case TCMPB1: > + case TCMPB2: case TCMPB3: > + index = (offset - TCMPB0)/0xC; > + value = s->timer[index].reg_tcmpb; > + break; > + > + case TCNTO0: case TCNTO1: > + case TCNTO2: case TCNTO3: case TCNTO4: > + index = (offset == TCNTO4) ? 4 : (offset - TCNTO0)/0xC; > + value = ptimer_get_count(s->timer[index].ptimer); > + break; > + > + case TINT_CSTAT: > + value = s->reg_tint_cstat; > + break; > + > + default: > + fprintf(stderr, > + "[exynos4210.pwm: bad read offset " TARGET_FMT_plx "]\n", > + offset); > + break; > + } > + return value; > +} > + > +/* > + * PWM Write > + */ > +static void exynos4210_pwm_write(void *opaque, target_phys_addr_t offset, > + uint64_t value, unsigned size) > +{ > + Exynos4210PWMState *s = (Exynos4210PWMState *)opaque; > + int index; > + uint32_t new_val; > + int i; > + > + switch (offset) { > + case TCFG0: case TCFG1: > + index = (offset - TCFG0)>>2; > + s->reg_tcfg[index] = value; > + > + /* update timers frequencies */ > + for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { > + exynos4210_pwm_update_freq(s, s->timer[i].id); > + } > + break; > + > + case TCON: > + for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { > + if ((value & TCON_TIMER_MANUAL_UPD(i)) > > + (s->reg_tcon & TCON_TIMER_MANUAL_UPD(i))) { > + /* > + * TCNTB and TCMPB are loaded into TCNT and TCMP. > + * Update timers. > + */ > + > + /* this will start timer to run, this ok, because > + * during processing start bit timer will be stopped > + * if needed */ > + ptimer_set_count(s->timer[i].ptimer, s->timer[i].reg_tcntb); > + DPRINTF("set timer %d count to %x\n", i, > + s->timer[i].reg_tcntb); > + } > + > + if ((value & TCON_TIMER_START(i)) > > + (s->reg_tcon & TCON_TIMER_START(i))) { > + /* changed to start */ > + ptimer_run(s->timer[i].ptimer, 1); > + DPRINTF("run timer %d\n", i); > + } > + > + if ((value & TCON_TIMER_START(i)) < > + (s->reg_tcon & TCON_TIMER_START(i))) { > + /* changed to stop */ > + ptimer_stop(s->timer[i].ptimer); > + DPRINTF("stop timer %d\n", i); > + } > + } > + s->reg_tcon = value; > + break; > + > + case TCNTB0: case TCNTB1: > + case TCNTB2: case TCNTB3: case TCNTB4: > + index = (offset - TCNTB0)/0xC; > + s->timer[index].reg_tcntb = value; > + break; > + > + case TCMPB0: case TCMPB1: > + case TCMPB2: case TCMPB3: > + index = (offset - TCMPB0)/0xC; > + s->timer[index].reg_tcmpb = value; > + break; > + > + case TINT_CSTAT: > + new_val = (s->reg_tint_cstat&0x3E0) + (0x1F & value); > + new_val &= ~(0x3E0 & value); > + > + for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { > + if ((new_val & TINT_CSTAT_STATUS(i)) < > + (s->reg_tint_cstat & TINT_CSTAT_STATUS(i))) { > + qemu_irq_lower(s->timer[i].irq); > + } > + } > + > + s->reg_tint_cstat = new_val; > + break; > + > + default: > + fprintf(stderr, > + "[exynos4210.pwm: bad write offset " TARGET_FMT_plx "]\n", > + offset); > + break; > + > + } > +} > + > +/* > + * Set default values to timer fields and registers > + */ > +static void exynos4210_pwm_reset(Exynos4210PWMState *s) > +{ > + int i; > + s->reg_tcfg[0] = 0x0101; > + s->reg_tcfg[1] = 0x0; > + s->reg_tcon = 0; > + s->reg_tint_cstat = 0; > + for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { > + s->timer[i].reg_tcmpb = 0; > + s->timer[i].reg_tcntb = 0; > + > + exynos4210_pwm_update_freq(s, s->timer[i].id); > + ptimer_stop(s->timer[i].ptimer); > + } > +} > + > +static const MemoryRegionOps exynos4210_pwm_ops = { > + .read = exynos4210_pwm_read, > + .write = exynos4210_pwm_write, > + .endianness = DEVICE_NATIVE_ENDIAN, > +}; > + > +/* > + * PWM timer initialization > + */ > +static int exynos4210_pwm_init(SysBusDevice *dev) > +{ > + Exynos4210PWMState *s = FROM_SYSBUS(Exynos4210PWMState, dev); > + int i; > + QEMUBH * bh[EXYNOS4210_PWM_TIMERS_NUM]; > + > + s->clk = ACLK_100; > + > + bh[0] = qemu_bh_new(exynos4210_pwm_tick0, s); > + bh[1] = qemu_bh_new(exynos4210_pwm_tick1, s); > + bh[2] = qemu_bh_new(exynos4210_pwm_tick2, s); > + bh[3] = qemu_bh_new(exynos4210_pwm_tick3, s); > + bh[4] = qemu_bh_new(exynos4210_pwm_tick4, s); > + > + for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { > + sysbus_init_irq(dev, &s->timer[i].irq); > + s->timer[i].ptimer = ptimer_init(bh[i]); > + s->timer[i].id = i; > + } > + > + memory_region_init_io(&s->iomem, &exynos4210_pwm_ops, s, > "exynos4210-pwm", > + EXYNOS4210_PWM_REG_MEM_SIZE); > + sysbus_init_mmio(dev, &s->iomem); > + > + exynos4210_pwm_reset(s); > + > + qemu_register_reset((QEMUResetHandler *)exynos4210_pwm_reset, s); > + vmstate_register(NULL, -1, &VMState_Exynos4210PWMState, s); Same remarks about reset, vmstate as in other patch review mail. > + return 0; > +} > + > +static void exynos4210_pwm_register_devices(void) > +{ > + sysbus_register_dev("exynos4210.pwm", sizeof(Exynos4210PWMState), > + exynos4210_pwm_init); > +} > + > +device_init(exynos4210_pwm_register_devices) > -- > 1.7.4.1 > >