linux/arch/arm/plat-samsung/s5p-time.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2011 Samsung Electronics Co., Ltd.
   3 *              http://www.samsung.com/
   4 *
   5 * S5P - Common hr-timer support
   6 *
   7 * This program is free software; you can redistribute it and/or modify
   8 * it under the terms of the GNU General Public License version 2 as
   9 * published by the Free Software Foundation.
  10*/
  11
  12#include <linux/interrupt.h>
  13#include <linux/irq.h>
  14#include <linux/err.h>
  15#include <linux/clk.h>
  16#include <linux/clockchips.h>
  17#include <linux/platform_device.h>
  18
  19#include <asm/smp_twd.h>
  20#include <asm/mach/time.h>
  21#include <asm/mach/arch.h>
  22#include <asm/mach/map.h>
  23#include <asm/sched_clock.h>
  24
  25#include <mach/map.h>
  26#include <plat/devs.h>
  27#include <plat/regs-timer.h>
  28#include <plat/s5p-time.h>
  29
  30static struct clk *tin_event;
  31static struct clk *tin_source;
  32static struct clk *tdiv_event;
  33static struct clk *tdiv_source;
  34static struct clk *timerclk;
  35static struct s5p_timer_source timer_source;
  36static unsigned long clock_count_per_tick;
  37static void s5p_timer_resume(void);
  38
  39static void s5p_time_stop(enum s5p_timer_mode mode)
  40{
  41        unsigned long tcon;
  42
  43        tcon = __raw_readl(S3C2410_TCON);
  44
  45        switch (mode) {
  46        case S5P_PWM0:
  47                tcon &= ~S3C2410_TCON_T0START;
  48                break;
  49
  50        case S5P_PWM1:
  51                tcon &= ~S3C2410_TCON_T1START;
  52                break;
  53
  54        case S5P_PWM2:
  55                tcon &= ~S3C2410_TCON_T2START;
  56                break;
  57
  58        case S5P_PWM3:
  59                tcon &= ~S3C2410_TCON_T3START;
  60                break;
  61
  62        case S5P_PWM4:
  63                tcon &= ~S3C2410_TCON_T4START;
  64                break;
  65
  66        default:
  67                printk(KERN_ERR "Invalid Timer %d\n", mode);
  68                break;
  69        }
  70        __raw_writel(tcon, S3C2410_TCON);
  71}
  72
  73static void s5p_time_setup(enum s5p_timer_mode mode, unsigned long tcnt)
  74{
  75        unsigned long tcon;
  76
  77        tcon = __raw_readl(S3C2410_TCON);
  78
  79        tcnt--;
  80
  81        switch (mode) {
  82        case S5P_PWM0:
  83                tcon &= ~(0x0f << 0);
  84                tcon |= S3C2410_TCON_T0MANUALUPD;
  85                break;
  86
  87        case S5P_PWM1:
  88                tcon &= ~(0x0f << 8);
  89                tcon |= S3C2410_TCON_T1MANUALUPD;
  90                break;
  91
  92        case S5P_PWM2:
  93                tcon &= ~(0x0f << 12);
  94                tcon |= S3C2410_TCON_T2MANUALUPD;
  95                break;
  96
  97        case S5P_PWM3:
  98                tcon &= ~(0x0f << 16);
  99                tcon |= S3C2410_TCON_T3MANUALUPD;
 100                break;
 101
 102        case S5P_PWM4:
 103                tcon &= ~(0x07 << 20);
 104                tcon |= S3C2410_TCON_T4MANUALUPD;
 105                break;
 106
 107        default:
 108                printk(KERN_ERR "Invalid Timer %d\n", mode);
 109                break;
 110        }
 111
 112        __raw_writel(tcnt, S3C2410_TCNTB(mode));
 113        __raw_writel(tcnt, S3C2410_TCMPB(mode));
 114        __raw_writel(tcon, S3C2410_TCON);
 115}
 116
 117static void s5p_time_start(enum s5p_timer_mode mode, bool periodic)
 118{
 119        unsigned long tcon;
 120
 121        tcon  = __raw_readl(S3C2410_TCON);
 122
 123        switch (mode) {
 124        case S5P_PWM0:
 125                tcon |= S3C2410_TCON_T0START;
 126                tcon &= ~S3C2410_TCON_T0MANUALUPD;
 127
 128                if (periodic)
 129                        tcon |= S3C2410_TCON_T0RELOAD;
 130                else
 131                        tcon &= ~S3C2410_TCON_T0RELOAD;
 132                break;
 133
 134        case S5P_PWM1:
 135                tcon |= S3C2410_TCON_T1START;
 136                tcon &= ~S3C2410_TCON_T1MANUALUPD;
 137
 138                if (periodic)
 139                        tcon |= S3C2410_TCON_T1RELOAD;
 140                else
 141                        tcon &= ~S3C2410_TCON_T1RELOAD;
 142                break;
 143
 144        case S5P_PWM2:
 145                tcon |= S3C2410_TCON_T2START;
 146                tcon &= ~S3C2410_TCON_T2MANUALUPD;
 147
 148                if (periodic)
 149                        tcon |= S3C2410_TCON_T2RELOAD;
 150                else
 151                        tcon &= ~S3C2410_TCON_T2RELOAD;
 152                break;
 153
 154        case S5P_PWM3:
 155                tcon |= S3C2410_TCON_T3START;
 156                tcon &= ~S3C2410_TCON_T3MANUALUPD;
 157
 158                if (periodic)
 159                        tcon |= S3C2410_TCON_T3RELOAD;
 160                else
 161                        tcon &= ~S3C2410_TCON_T3RELOAD;
 162                break;
 163
 164        case S5P_PWM4:
 165                tcon |= S3C2410_TCON_T4START;
 166                tcon &= ~S3C2410_TCON_T4MANUALUPD;
 167
 168                if (periodic)
 169                        tcon |= S3C2410_TCON_T4RELOAD;
 170                else
 171                        tcon &= ~S3C2410_TCON_T4RELOAD;
 172                break;
 173
 174        default:
 175                printk(KERN_ERR "Invalid Timer %d\n", mode);
 176                break;
 177        }
 178        __raw_writel(tcon, S3C2410_TCON);
 179}
 180
 181static int s5p_set_next_event(unsigned long cycles,
 182                                struct clock_event_device *evt)
 183{
 184        s5p_time_setup(timer_source.event_id, cycles);
 185        s5p_time_start(timer_source.event_id, NON_PERIODIC);
 186
 187        return 0;
 188}
 189
 190static void s5p_set_mode(enum clock_event_mode mode,
 191                                struct clock_event_device *evt)
 192{
 193        s5p_time_stop(timer_source.event_id);
 194
 195        switch (mode) {
 196        case CLOCK_EVT_MODE_PERIODIC:
 197                s5p_time_setup(timer_source.event_id, clock_count_per_tick);
 198                s5p_time_start(timer_source.event_id, PERIODIC);
 199                break;
 200
 201        case CLOCK_EVT_MODE_ONESHOT:
 202                break;
 203
 204        case CLOCK_EVT_MODE_UNUSED:
 205        case CLOCK_EVT_MODE_SHUTDOWN:
 206                break;
 207
 208        case CLOCK_EVT_MODE_RESUME:
 209                s5p_timer_resume();
 210                break;
 211        }
 212}
 213
 214static void s5p_timer_resume(void)
 215{
 216        /* event timer restart */
 217        s5p_time_setup(timer_source.event_id, clock_count_per_tick);
 218        s5p_time_start(timer_source.event_id, PERIODIC);
 219
 220        /* source timer restart */
 221        s5p_time_setup(timer_source.source_id, TCNT_MAX);
 222        s5p_time_start(timer_source.source_id, PERIODIC);
 223}
 224
 225void __init s5p_set_timer_source(enum s5p_timer_mode event,
 226                                 enum s5p_timer_mode source)
 227{
 228        s3c_device_timer[event].dev.bus = &platform_bus_type;
 229        s3c_device_timer[source].dev.bus = &platform_bus_type;
 230
 231        timer_source.event_id = event;
 232        timer_source.source_id = source;
 233}
 234
 235static struct clock_event_device time_event_device = {
 236        .name           = "s5p_event_timer",
 237        .features       = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
 238        .rating         = 200,
 239        .set_next_event = s5p_set_next_event,
 240        .set_mode       = s5p_set_mode,
 241};
 242
 243static irqreturn_t s5p_clock_event_isr(int irq, void *dev_id)
 244{
 245        struct clock_event_device *evt = dev_id;
 246
 247        evt->event_handler(evt);
 248
 249        return IRQ_HANDLED;
 250}
 251
 252static struct irqaction s5p_clock_event_irq = {
 253        .name           = "s5p_time_irq",
 254        .flags          = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
 255        .handler        = s5p_clock_event_isr,
 256        .dev_id         = &time_event_device,
 257};
 258
 259static void __init s5p_clockevent_init(void)
 260{
 261        unsigned long pclk;
 262        unsigned long clock_rate;
 263        unsigned int irq_number;
 264        struct clk *tscaler;
 265
 266        pclk = clk_get_rate(timerclk);
 267
 268        tscaler = clk_get_parent(tdiv_event);
 269
 270        clk_set_rate(tscaler, pclk / 2);
 271        clk_set_rate(tdiv_event, pclk / 2);
 272        clk_set_parent(tin_event, tdiv_event);
 273
 274        clock_rate = clk_get_rate(tin_event);
 275        clock_count_per_tick = clock_rate / HZ;
 276
 277        clockevents_calc_mult_shift(&time_event_device,
 278                                    clock_rate, S5PTIMER_MIN_RANGE);
 279        time_event_device.max_delta_ns =
 280                clockevent_delta2ns(-1, &time_event_device);
 281        time_event_device.min_delta_ns =
 282                clockevent_delta2ns(1, &time_event_device);
 283
 284        time_event_device.cpumask = cpumask_of(0);
 285        clockevents_register_device(&time_event_device);
 286
 287        irq_number = timer_source.event_id + IRQ_TIMER0;
 288        setup_irq(irq_number, &s5p_clock_event_irq);
 289}
 290
 291static void __iomem *s5p_timer_reg(void)
 292{
 293        unsigned long offset = 0;
 294
 295        switch (timer_source.source_id) {
 296        case S5P_PWM0:
 297        case S5P_PWM1:
 298        case S5P_PWM2:
 299        case S5P_PWM3:
 300                offset = (timer_source.source_id * 0x0c) + 0x14;
 301                break;
 302
 303        case S5P_PWM4:
 304                offset = 0x40;
 305                break;
 306
 307        default:
 308                printk(KERN_ERR "Invalid Timer %d\n", timer_source.source_id);
 309                return NULL;
 310        }
 311
 312        return S3C_TIMERREG(offset);
 313}
 314
 315/*
 316 * Override the global weak sched_clock symbol with this
 317 * local implementation which uses the clocksource to get some
 318 * better resolution when scheduling the kernel. We accept that
 319 * this wraps around for now, since it is just a relative time
 320 * stamp. (Inspired by U300 implementation.)
 321 */
 322static u32 notrace s5p_read_sched_clock(void)
 323{
 324        void __iomem *reg = s5p_timer_reg();
 325
 326        if (!reg)
 327                return 0;
 328
 329        return ~__raw_readl(reg);
 330}
 331
 332static void __init s5p_clocksource_init(void)
 333{
 334        unsigned long pclk;
 335        unsigned long clock_rate;
 336
 337        pclk = clk_get_rate(timerclk);
 338
 339        clk_set_rate(tdiv_source, pclk / 2);
 340        clk_set_parent(tin_source, tdiv_source);
 341
 342        clock_rate = clk_get_rate(tin_source);
 343
 344        s5p_time_setup(timer_source.source_id, TCNT_MAX);
 345        s5p_time_start(timer_source.source_id, PERIODIC);
 346
 347        setup_sched_clock(s5p_read_sched_clock, 32, clock_rate);
 348
 349        if (clocksource_mmio_init(s5p_timer_reg(), "s5p_clocksource_timer",
 350                        clock_rate, 250, 32, clocksource_mmio_readl_down))
 351                panic("s5p_clocksource_timer: can't register clocksource\n");
 352}
 353
 354static void __init s5p_timer_resources(void)
 355{
 356
 357        unsigned long event_id = timer_source.event_id;
 358        unsigned long source_id = timer_source.source_id;
 359        char devname[15];
 360
 361        timerclk = clk_get(NULL, "timers");
 362        if (IS_ERR(timerclk))
 363                panic("failed to get timers clock for timer");
 364
 365        clk_enable(timerclk);
 366
 367        sprintf(devname, "s3c24xx-pwm.%lu", event_id);
 368        s3c_device_timer[event_id].id = event_id;
 369        s3c_device_timer[event_id].dev.init_name = devname;
 370
 371        tin_event = clk_get(&s3c_device_timer[event_id].dev, "pwm-tin");
 372        if (IS_ERR(tin_event))
 373                panic("failed to get pwm-tin clock for event timer");
 374
 375        tdiv_event = clk_get(&s3c_device_timer[event_id].dev, "pwm-tdiv");
 376        if (IS_ERR(tdiv_event))
 377                panic("failed to get pwm-tdiv clock for event timer");
 378
 379        clk_enable(tin_event);
 380
 381        sprintf(devname, "s3c24xx-pwm.%lu", source_id);
 382        s3c_device_timer[source_id].id = source_id;
 383        s3c_device_timer[source_id].dev.init_name = devname;
 384
 385        tin_source = clk_get(&s3c_device_timer[source_id].dev, "pwm-tin");
 386        if (IS_ERR(tin_source))
 387                panic("failed to get pwm-tin clock for source timer");
 388
 389        tdiv_source = clk_get(&s3c_device_timer[source_id].dev, "pwm-tdiv");
 390        if (IS_ERR(tdiv_source))
 391                panic("failed to get pwm-tdiv clock for source timer");
 392
 393        clk_enable(tin_source);
 394}
 395
 396static void __init s5p_timer_init(void)
 397{
 398        s5p_timer_resources();
 399        s5p_clockevent_init();
 400        s5p_clocksource_init();
 401}
 402
 403struct sys_timer s5p_timer = {
 404        .init           = s5p_timer_init,
 405};
 406