linux/arch/arm/plat-samsung/samsung-time.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2011 Samsung Electronics Co., Ltd.
   3 *              http://www.samsung.com/
   4 *
   5 * samsung - Common hr-timer support (s3c and s5p)
   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#include <linux/sched_clock.h>
  19
  20#include <asm/smp_twd.h>
  21#include <asm/mach/time.h>
  22#include <asm/mach/arch.h>
  23#include <asm/mach/map.h>
  24
  25#include <mach/map.h>
  26#include <plat/devs.h>
  27#include <plat/regs-timer.h>
  28#include <plat/samsung-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 samsung_timer_source timer_source;
  36static unsigned long clock_count_per_tick;
  37static void samsung_timer_resume(void);
  38
  39static void samsung_time_stop(enum samsung_timer_mode mode)
  40{
  41        unsigned long tcon;
  42
  43        tcon = __raw_readl(S3C2410_TCON);
  44
  45        switch (mode) {
  46        case SAMSUNG_PWM0:
  47                tcon &= ~S3C2410_TCON_T0START;
  48                break;
  49
  50        case SAMSUNG_PWM1:
  51                tcon &= ~S3C2410_TCON_T1START;
  52                break;
  53
  54        case SAMSUNG_PWM2:
  55                tcon &= ~S3C2410_TCON_T2START;
  56                break;
  57
  58        case SAMSUNG_PWM3:
  59                tcon &= ~S3C2410_TCON_T3START;
  60                break;
  61
  62        case SAMSUNG_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 samsung_time_setup(enum samsung_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 SAMSUNG_PWM0:
  83                tcon &= ~(0x0f << 0);
  84                tcon |= S3C2410_TCON_T0MANUALUPD;
  85                break;
  86
  87        case SAMSUNG_PWM1:
  88                tcon &= ~(0x0f << 8);
  89                tcon |= S3C2410_TCON_T1MANUALUPD;
  90                break;
  91
  92        case SAMSUNG_PWM2:
  93                tcon &= ~(0x0f << 12);
  94                tcon |= S3C2410_TCON_T2MANUALUPD;
  95                break;
  96
  97        case SAMSUNG_PWM3:
  98                tcon &= ~(0x0f << 16);
  99                tcon |= S3C2410_TCON_T3MANUALUPD;
 100                break;
 101
 102        case SAMSUNG_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 samsung_time_start(enum samsung_timer_mode mode, bool periodic)
 118{
 119        unsigned long tcon;
 120
 121        tcon  = __raw_readl(S3C2410_TCON);
 122
 123        switch (mode) {
 124        case SAMSUNG_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 SAMSUNG_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 SAMSUNG_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 SAMSUNG_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 SAMSUNG_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 samsung_set_next_event(unsigned long cycles,
 182                                struct clock_event_device *evt)
 183{
 184        samsung_time_setup(timer_source.event_id, cycles);
 185        samsung_time_start(timer_source.event_id, NON_PERIODIC);
 186
 187        return 0;
 188}
 189
 190static void samsung_set_mode(enum clock_event_mode mode,
 191                                struct clock_event_device *evt)
 192{
 193        samsung_time_stop(timer_source.event_id);
 194
 195        switch (mode) {
 196        case CLOCK_EVT_MODE_PERIODIC:
 197                samsung_time_setup(timer_source.event_id, clock_count_per_tick);
 198                samsung_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                samsung_timer_resume();
 210                break;
 211        }
 212}
 213
 214static void samsung_timer_resume(void)
 215{
 216        /* event timer restart */
 217        samsung_time_setup(timer_source.event_id, clock_count_per_tick);
 218        samsung_time_start(timer_source.event_id, PERIODIC);
 219
 220        /* source timer restart */
 221        samsung_time_setup(timer_source.source_id, TCNT_MAX);
 222        samsung_time_start(timer_source.source_id, PERIODIC);
 223}
 224
 225void __init samsung_set_timer_source(enum samsung_timer_mode event,
 226                                 enum samsung_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           = "samsung_event_timer",
 237        .features       = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
 238        .rating         = 200,
 239        .set_next_event = samsung_set_next_event,
 240        .set_mode       = samsung_set_mode,
 241};
 242
 243static irqreturn_t samsung_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 samsung_clock_event_irq = {
 253        .name           = "samsung_time_irq",
 254        .flags          = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
 255        .handler        = samsung_clock_event_isr,
 256        .dev_id         = &time_event_device,
 257};
 258
 259static void __init samsung_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 / TSCALER_DIV);
 271        clk_set_rate(tdiv_event, pclk / TDIV);
 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        time_event_device.cpumask = cpumask_of(0);
 278        clockevents_config_and_register(&time_event_device, clock_rate, 1, -1);
 279
 280        irq_number = timer_source.event_id + IRQ_TIMER0;
 281        setup_irq(irq_number, &samsung_clock_event_irq);
 282}
 283
 284static void __iomem *samsung_timer_reg(void)
 285{
 286        unsigned long offset = 0;
 287
 288        switch (timer_source.source_id) {
 289        case SAMSUNG_PWM0:
 290        case SAMSUNG_PWM1:
 291        case SAMSUNG_PWM2:
 292        case SAMSUNG_PWM3:
 293                offset = (timer_source.source_id * 0x0c) + 0x14;
 294                break;
 295
 296        case SAMSUNG_PWM4:
 297                offset = 0x40;
 298                break;
 299
 300        default:
 301                printk(KERN_ERR "Invalid Timer %d\n", timer_source.source_id);
 302                return NULL;
 303        }
 304
 305        return S3C_TIMERREG(offset);
 306}
 307
 308/*
 309 * Override the global weak sched_clock symbol with this
 310 * local implementation which uses the clocksource to get some
 311 * better resolution when scheduling the kernel. We accept that
 312 * this wraps around for now, since it is just a relative time
 313 * stamp. (Inspired by U300 implementation.)
 314 */
 315static u32 notrace samsung_read_sched_clock(void)
 316{
 317        void __iomem *reg = samsung_timer_reg();
 318
 319        if (!reg)
 320                return 0;
 321
 322        return ~__raw_readl(reg);
 323}
 324
 325static void __init samsung_clocksource_init(void)
 326{
 327        unsigned long pclk;
 328        unsigned long clock_rate;
 329
 330        pclk = clk_get_rate(timerclk);
 331
 332        clk_set_rate(tdiv_source, pclk / TDIV);
 333        clk_set_parent(tin_source, tdiv_source);
 334
 335        clock_rate = clk_get_rate(tin_source);
 336
 337        samsung_time_setup(timer_source.source_id, TCNT_MAX);
 338        samsung_time_start(timer_source.source_id, PERIODIC);
 339
 340        setup_sched_clock(samsung_read_sched_clock, TSIZE, clock_rate);
 341
 342        if (clocksource_mmio_init(samsung_timer_reg(), "samsung_clocksource_timer",
 343                        clock_rate, 250, TSIZE, clocksource_mmio_readl_down))
 344                panic("samsung_clocksource_timer: can't register clocksource\n");
 345}
 346
 347static void __init samsung_timer_resources(void)
 348{
 349
 350        unsigned long event_id = timer_source.event_id;
 351        unsigned long source_id = timer_source.source_id;
 352        char devname[15];
 353
 354        timerclk = clk_get(NULL, "timers");
 355        if (IS_ERR(timerclk))
 356                panic("failed to get timers clock for timer");
 357
 358        clk_enable(timerclk);
 359
 360        sprintf(devname, "s3c24xx-pwm.%lu", event_id);
 361        s3c_device_timer[event_id].id = event_id;
 362        s3c_device_timer[event_id].dev.init_name = devname;
 363
 364        tin_event = clk_get(&s3c_device_timer[event_id].dev, "pwm-tin");
 365        if (IS_ERR(tin_event))
 366                panic("failed to get pwm-tin clock for event timer");
 367
 368        tdiv_event = clk_get(&s3c_device_timer[event_id].dev, "pwm-tdiv");
 369        if (IS_ERR(tdiv_event))
 370                panic("failed to get pwm-tdiv clock for event timer");
 371
 372        clk_enable(tin_event);
 373
 374        sprintf(devname, "s3c24xx-pwm.%lu", source_id);
 375        s3c_device_timer[source_id].id = source_id;
 376        s3c_device_timer[source_id].dev.init_name = devname;
 377
 378        tin_source = clk_get(&s3c_device_timer[source_id].dev, "pwm-tin");
 379        if (IS_ERR(tin_source))
 380                panic("failed to get pwm-tin clock for source timer");
 381
 382        tdiv_source = clk_get(&s3c_device_timer[source_id].dev, "pwm-tdiv");
 383        if (IS_ERR(tdiv_source))
 384                panic("failed to get pwm-tdiv clock for source timer");
 385
 386        clk_enable(tin_source);
 387}
 388
 389void __init samsung_timer_init(void)
 390{
 391        samsung_timer_resources();
 392        samsung_clockevent_init();
 393        samsung_clocksource_init();
 394}
 395