linux/arch/arm/plat-s3c/pwm.c
<<
>>
Prefs
   1/* arch/arm/plat-s3c/pwm.c
   2 *
   3 * Copyright (c) 2007 Ben Dooks
   4 * Copyright (c) 2008 Simtec Electronics
   5 *      Ben Dooks <ben@simtec.co.uk>, <ben-linux@fluff.org>
   6 *
   7 * S3C series PWM device core
   8 *
   9 * This program is free software; you can redistribute it and/or modify
  10 * it under the terms of the GNU General Public License as published by
  11 * the Free Software Foundation; either version 2 of the License.
  12*/
  13
  14#include <linux/module.h>
  15#include <linux/kernel.h>
  16#include <linux/platform_device.h>
  17#include <linux/err.h>
  18#include <linux/clk.h>
  19#include <linux/io.h>
  20#include <linux/pwm.h>
  21
  22#include <mach/irqs.h>
  23#include <mach/map.h>
  24
  25#include <plat/devs.h>
  26#include <plat/regs-timer.h>
  27
  28struct pwm_device {
  29        struct list_head         list;
  30        struct platform_device  *pdev;
  31
  32        struct clk              *clk_div;
  33        struct clk              *clk;
  34        const char              *label;
  35
  36        unsigned int             period_ns;
  37        unsigned int             duty_ns;
  38
  39        unsigned char            tcon_base;
  40        unsigned char            running;
  41        unsigned char            use_count;
  42        unsigned char            pwm_id;
  43};
  44
  45#define pwm_dbg(_pwm, msg...) dev_dbg(&(_pwm)->pdev->dev, msg)
  46
  47static struct clk *clk_scaler[2];
  48
  49/* Standard setup for a timer block. */
  50
  51#define TIMER_RESOURCE_SIZE (1)
  52
  53#define TIMER_RESOURCE(_tmr, _irq)                      \
  54        (struct resource [TIMER_RESOURCE_SIZE]) {       \
  55                [0] = {                                 \
  56                        .start  = _irq,                 \
  57                        .end    = _irq,                 \
  58                        .flags  = IORESOURCE_IRQ        \
  59                }                                       \
  60        }
  61
  62#define DEFINE_S3C_TIMER(_tmr_no, _irq)                 \
  63        .name           = "s3c24xx-pwm",                \
  64        .id             = _tmr_no,                      \
  65        .num_resources  = TIMER_RESOURCE_SIZE,          \
  66        .resource       = TIMER_RESOURCE(_tmr_no, _irq),        \
  67
  68/* since we already have an static mapping for the timer, we do not
  69 * bother setting any IO resource for the base.
  70 */
  71
  72struct platform_device s3c_device_timer[] = {
  73        [0] = { DEFINE_S3C_TIMER(0, IRQ_TIMER0) },
  74        [1] = { DEFINE_S3C_TIMER(1, IRQ_TIMER1) },
  75        [2] = { DEFINE_S3C_TIMER(2, IRQ_TIMER2) },
  76        [3] = { DEFINE_S3C_TIMER(3, IRQ_TIMER3) },
  77        [4] = { DEFINE_S3C_TIMER(4, IRQ_TIMER4) },
  78};
  79
  80static inline int pwm_is_tdiv(struct pwm_device *pwm)
  81{
  82        return clk_get_parent(pwm->clk) == pwm->clk_div;
  83}
  84
  85static DEFINE_MUTEX(pwm_lock);
  86static LIST_HEAD(pwm_list);
  87
  88struct pwm_device *pwm_request(int pwm_id, const char *label)
  89{
  90        struct pwm_device *pwm;
  91        int found = 0;
  92
  93        mutex_lock(&pwm_lock);
  94
  95        list_for_each_entry(pwm, &pwm_list, list) {
  96                if (pwm->pwm_id == pwm_id) {
  97                        found = 1;
  98                        break;
  99                }
 100        }
 101
 102        if (found) {
 103                if (pwm->use_count == 0) {
 104                        pwm->use_count = 1;
 105                        pwm->label = label;
 106                } else
 107                        pwm = ERR_PTR(-EBUSY);
 108        } else
 109                pwm = ERR_PTR(-ENOENT);
 110
 111        mutex_unlock(&pwm_lock);
 112        return pwm;
 113}
 114
 115EXPORT_SYMBOL(pwm_request);
 116
 117
 118void pwm_free(struct pwm_device *pwm)
 119{
 120        mutex_lock(&pwm_lock);
 121
 122        if (pwm->use_count) {
 123                pwm->use_count--;
 124                pwm->label = NULL;
 125        } else
 126                printk(KERN_ERR "PWM%d device already freed\n", pwm->pwm_id);
 127
 128        mutex_unlock(&pwm_lock);
 129}
 130
 131EXPORT_SYMBOL(pwm_free);
 132
 133#define pwm_tcon_start(pwm) (1 << (pwm->tcon_base + 0))
 134#define pwm_tcon_invert(pwm) (1 << (pwm->tcon_base + 2))
 135#define pwm_tcon_autoreload(pwm) (1 << (pwm->tcon_base + 3))
 136#define pwm_tcon_manulupdate(pwm) (1 << (pwm->tcon_base + 1))
 137
 138int pwm_enable(struct pwm_device *pwm)
 139{
 140        unsigned long flags;
 141        unsigned long tcon;
 142
 143        local_irq_save(flags);
 144
 145        tcon = __raw_readl(S3C2410_TCON);
 146        tcon |= pwm_tcon_start(pwm);
 147        __raw_writel(tcon, S3C2410_TCON);
 148
 149        local_irq_restore(flags);
 150
 151        pwm->running = 1;
 152        return 0;
 153}
 154
 155EXPORT_SYMBOL(pwm_enable);
 156
 157void pwm_disable(struct pwm_device *pwm)
 158{
 159        unsigned long flags;
 160        unsigned long tcon;
 161
 162        local_irq_save(flags);
 163
 164        tcon = __raw_readl(S3C2410_TCON);
 165        tcon &= ~pwm_tcon_start(pwm);
 166        __raw_writel(tcon, S3C2410_TCON);
 167
 168        local_irq_restore(flags);
 169
 170        pwm->running = 0;
 171}
 172
 173EXPORT_SYMBOL(pwm_disable);
 174
 175static unsigned long pwm_calc_tin(struct pwm_device *pwm, unsigned long freq)
 176{
 177        unsigned long tin_parent_rate;
 178        unsigned int div;
 179
 180        tin_parent_rate = clk_get_rate(clk_get_parent(pwm->clk_div));
 181        pwm_dbg(pwm, "tin parent at %lu\n", tin_parent_rate);
 182
 183        for (div = 2; div <= 16; div *= 2) {
 184                if ((tin_parent_rate / (div << 16)) < freq)
 185                        return tin_parent_rate / div;
 186        }
 187
 188        return tin_parent_rate / 16;
 189}
 190
 191#define NS_IN_HZ (1000000000UL)
 192
 193int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
 194{
 195        unsigned long tin_rate;
 196        unsigned long tin_ns;
 197        unsigned long period;
 198        unsigned long flags;
 199        unsigned long tcon;
 200        unsigned long tcnt;
 201        long tcmp;
 202
 203        /* We currently avoid using 64bit arithmetic by using the
 204         * fact that anything faster than 1Hz is easily representable
 205         * by 32bits. */
 206
 207        if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ)
 208                return -ERANGE;
 209
 210        if (duty_ns > period_ns)
 211                return -EINVAL;
 212
 213        if (period_ns == pwm->period_ns &&
 214            duty_ns == pwm->duty_ns)
 215                return 0;
 216
 217        /* The TCMP and TCNT can be read without a lock, they're not
 218         * shared between the timers. */
 219
 220        tcmp = __raw_readl(S3C2410_TCMPB(pwm->pwm_id));
 221        tcnt = __raw_readl(S3C2410_TCNTB(pwm->pwm_id));
 222
 223        period = NS_IN_HZ / period_ns;
 224
 225        pwm_dbg(pwm, "duty_ns=%d, period_ns=%d (%lu)\n",
 226                duty_ns, period_ns, period);
 227
 228        /* Check to see if we are changing the clock rate of the PWM */
 229
 230        if (pwm->period_ns != period_ns) {
 231                if (pwm_is_tdiv(pwm)) {
 232                        tin_rate = pwm_calc_tin(pwm, period);
 233                        clk_set_rate(pwm->clk_div, tin_rate);
 234                } else
 235                        tin_rate = clk_get_rate(pwm->clk);
 236
 237                pwm->period_ns = period_ns;
 238
 239                pwm_dbg(pwm, "tin_rate=%lu\n", tin_rate);
 240
 241                tin_ns = NS_IN_HZ / tin_rate;
 242                tcnt = period_ns / tin_ns;
 243        } else
 244                tin_ns = NS_IN_HZ / clk_get_rate(pwm->clk);
 245
 246        /* Note, counters count down */
 247
 248        tcmp = duty_ns / tin_ns;
 249        tcmp = tcnt - tcmp;
 250        /* the pwm hw only checks the compare register after a decrement,
 251           so the pin never toggles if tcmp = tcnt */
 252        if (tcmp == tcnt)
 253                tcmp--;
 254
 255        pwm_dbg(pwm, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt);
 256
 257        if (tcmp < 0)
 258                tcmp = 0;
 259
 260        /* Update the PWM register block. */
 261
 262        local_irq_save(flags);
 263
 264        __raw_writel(tcmp, S3C2410_TCMPB(pwm->pwm_id));
 265        __raw_writel(tcnt, S3C2410_TCNTB(pwm->pwm_id));
 266
 267        tcon = __raw_readl(S3C2410_TCON);
 268        tcon |= pwm_tcon_manulupdate(pwm);
 269        tcon |= pwm_tcon_autoreload(pwm);
 270        __raw_writel(tcon, S3C2410_TCON);
 271
 272        tcon &= ~pwm_tcon_manulupdate(pwm);
 273        __raw_writel(tcon, S3C2410_TCON);
 274
 275        local_irq_restore(flags);
 276
 277        return 0;
 278}
 279
 280EXPORT_SYMBOL(pwm_config);
 281
 282static int pwm_register(struct pwm_device *pwm)
 283{
 284        pwm->duty_ns = -1;
 285        pwm->period_ns = -1;
 286
 287        mutex_lock(&pwm_lock);
 288        list_add_tail(&pwm->list, &pwm_list);
 289        mutex_unlock(&pwm_lock);
 290
 291        return 0;
 292}
 293
 294static int s3c_pwm_probe(struct platform_device *pdev)
 295{
 296        struct device *dev = &pdev->dev;
 297        struct pwm_device *pwm;
 298        unsigned long flags;
 299        unsigned long tcon;
 300        unsigned int id = pdev->id;
 301        int ret;
 302
 303        if (id == 4) {
 304                dev_err(dev, "TIMER4 is currently not supported\n");
 305                return -ENXIO;
 306        }
 307
 308        pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
 309        if (pwm == NULL) {
 310                dev_err(dev, "failed to allocate pwm_device\n");
 311                return -ENOMEM;
 312        }
 313
 314        pwm->pdev = pdev;
 315        pwm->pwm_id = id;
 316
 317        /* calculate base of control bits in TCON */
 318        pwm->tcon_base = id == 0 ? 0 : (id * 4) + 4;
 319
 320        pwm->clk = clk_get(dev, "pwm-tin");
 321        if (IS_ERR(pwm->clk)) {
 322                dev_err(dev, "failed to get pwm tin clk\n");
 323                ret = PTR_ERR(pwm->clk);
 324                goto err_alloc;
 325        }
 326
 327        pwm->clk_div = clk_get(dev, "pwm-tdiv");
 328        if (IS_ERR(pwm->clk_div)) {
 329                dev_err(dev, "failed to get pwm tdiv clk\n");
 330                ret = PTR_ERR(pwm->clk_div);
 331                goto err_clk_tin;
 332        }
 333
 334        local_irq_save(flags);
 335
 336        tcon = __raw_readl(S3C2410_TCON);
 337        tcon |= pwm_tcon_invert(pwm);
 338        __raw_writel(tcon, S3C2410_TCON);
 339
 340        local_irq_restore(flags);
 341
 342
 343        ret = pwm_register(pwm);
 344        if (ret) {
 345                dev_err(dev, "failed to register pwm\n");
 346                goto err_clk_tdiv;
 347        }
 348
 349        pwm_dbg(pwm, "config bits %02x\n",
 350                (__raw_readl(S3C2410_TCON) >> pwm->tcon_base) & 0x0f);
 351
 352        dev_info(dev, "tin at %lu, tdiv at %lu, tin=%sclk, base %d\n",
 353                 clk_get_rate(pwm->clk),
 354                 clk_get_rate(pwm->clk_div),
 355                 pwm_is_tdiv(pwm) ? "div" : "ext", pwm->tcon_base);
 356
 357        platform_set_drvdata(pdev, pwm);
 358        return 0;
 359
 360 err_clk_tdiv:
 361        clk_put(pwm->clk_div);
 362
 363 err_clk_tin:
 364        clk_put(pwm->clk);
 365
 366 err_alloc:
 367        kfree(pwm);
 368        return ret;
 369}
 370
 371static int s3c_pwm_remove(struct platform_device *pdev)
 372{
 373        struct pwm_device *pwm = platform_get_drvdata(pdev);
 374
 375        clk_put(pwm->clk_div);
 376        clk_put(pwm->clk);
 377        kfree(pwm);
 378
 379        return 0;
 380}
 381
 382static struct platform_driver s3c_pwm_driver = {
 383        .driver         = {
 384                .name   = "s3c24xx-pwm",
 385                .owner  = THIS_MODULE,
 386        },
 387        .probe          = s3c_pwm_probe,
 388        .remove         = __devexit_p(s3c_pwm_remove),
 389};
 390
 391static int __init pwm_init(void)
 392{
 393        int ret;
 394
 395        clk_scaler[0] = clk_get(NULL, "pwm-scaler0");
 396        clk_scaler[1] = clk_get(NULL, "pwm-scaler1");
 397
 398        if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) {
 399                printk(KERN_ERR "%s: failed to get scaler clocks\n", __func__);
 400                return -EINVAL;
 401        }
 402
 403        ret = platform_driver_register(&s3c_pwm_driver);
 404        if (ret)
 405                printk(KERN_ERR "%s: failed to add pwm driver\n", __func__);
 406
 407        return ret;
 408}
 409
 410arch_initcall(pwm_init);
 411