linux/drivers/staging/comedi/drivers/8253.h
<<
>>
Prefs
   1/*
   2    comedi/drivers/8253.h
   3    Header file for 8253
   4
   5    COMEDI - Linux Control and Measurement Device Interface
   6    Copyright (C) 2000 David A. Schleef <ds@schleef.org>
   7
   8    This program is free software; you can redistribute it and/or modify
   9    it under the terms of the GNU General Public License as published by
  10    the Free Software Foundation; either version 2 of the License, or
  11    (at your option) any later version.
  12
  13    This program is distributed in the hope that it will be useful,
  14    but WITHOUT ANY WARRANTY; without even the implied warranty of
  15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16    GNU General Public License for more details.
  17
  18    You should have received a copy of the GNU General Public License
  19    along with this program; if not, write to the Free Software
  20    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  21
  22*/
  23
  24#ifndef _8253_H
  25#define _8253_H
  26
  27#include "../comedi.h"
  28
  29#define i8253_cascade_ns_to_timer i8253_cascade_ns_to_timer_2div
  30
  31static inline void i8253_cascade_ns_to_timer_2div_old(int i8253_osc_base,
  32                                                      unsigned int *d1,
  33                                                      unsigned int *d2,
  34                                                      unsigned int *nanosec,
  35                                                      int round_mode)
  36{
  37        int divider;
  38        int div1, div2;
  39        int div1_glb, div2_glb, ns_glb;
  40        int div1_lub, div2_lub, ns_lub;
  41        int ns;
  42
  43        divider = (*nanosec + i8253_osc_base / 2) / i8253_osc_base;
  44
  45        /* find 2 integers 1<={x,y}<=65536 such that x*y is
  46           close to divider */
  47
  48        div1_lub = div2_lub = 0;
  49        div1_glb = div2_glb = 0;
  50
  51        ns_glb = 0;
  52        ns_lub = 0xffffffff;
  53
  54        div2 = 0x10000;
  55        for (div1 = divider / 65536 + 1; div1 < div2; div1++) {
  56                div2 = divider / div1;
  57
  58                ns = i8253_osc_base * div1 * div2;
  59                if (ns <= *nanosec && ns > ns_glb) {
  60                        ns_glb = ns;
  61                        div1_glb = div1;
  62                        div2_glb = div2;
  63                }
  64
  65                div2++;
  66                if (div2 <= 65536) {
  67                        ns = i8253_osc_base * div1 * div2;
  68                        if (ns > *nanosec && ns < ns_lub) {
  69                                ns_lub = ns;
  70                                div1_lub = div1;
  71                                div2_lub = div2;
  72                        }
  73                }
  74        }
  75
  76        *nanosec = div1_lub * div2_lub * i8253_osc_base;
  77        *d1 = div1_lub & 0xffff;
  78        *d2 = div2_lub & 0xffff;
  79        return;
  80}
  81
  82static inline void i8253_cascade_ns_to_timer_power(int i8253_osc_base,
  83                                                   unsigned int *d1,
  84                                                   unsigned int *d2,
  85                                                   unsigned int *nanosec,
  86                                                   int round_mode)
  87{
  88        int div1, div2;
  89        int base;
  90
  91        for (div1 = 2; div1 <= (1 << 16); div1 <<= 1) {
  92                base = i8253_osc_base * div1;
  93                round_mode &= TRIG_ROUND_MASK;
  94                switch (round_mode) {
  95                case TRIG_ROUND_NEAREST:
  96                default:
  97                        div2 = (*nanosec + base / 2) / base;
  98                        break;
  99                case TRIG_ROUND_DOWN:
 100                        div2 = (*nanosec) / base;
 101                        break;
 102                case TRIG_ROUND_UP:
 103                        div2 = (*nanosec + base - 1) / base;
 104                        break;
 105                }
 106                if (div2 < 2)
 107                        div2 = 2;
 108                if (div2 <= 65536) {
 109                        *nanosec = div2 * base;
 110                        *d1 = div1 & 0xffff;
 111                        *d2 = div2 & 0xffff;
 112                        return;
 113                }
 114        }
 115
 116        /* shouldn't get here */
 117        div1 = 0x10000;
 118        div2 = 0x10000;
 119        *nanosec = div1 * div2 * i8253_osc_base;
 120        *d1 = div1 & 0xffff;
 121        *d2 = div2 & 0xffff;
 122}
 123
 124static inline void i8253_cascade_ns_to_timer_2div(int i8253_osc_base,
 125                                                  unsigned int *d1,
 126                                                  unsigned int *d2,
 127                                                  unsigned int *nanosec,
 128                                                  int round_mode)
 129{
 130        unsigned int divider;
 131        unsigned int div1, div2;
 132        unsigned int div1_glb, div2_glb, ns_glb;
 133        unsigned int div1_lub, div2_lub, ns_lub;
 134        unsigned int ns;
 135        unsigned int start;
 136        unsigned int ns_low, ns_high;
 137        static const unsigned int max_count = 0x10000;
 138        /* exit early if everything is already correct (this can save time
 139         * since this function may be called repeatedly during command tests
 140         * and execution) */
 141        div1 = *d1 ? *d1 : max_count;
 142        div2 = *d2 ? *d2 : max_count;
 143        divider = div1 * div2;
 144        if (div1 * div2 * i8253_osc_base == *nanosec &&
 145            div1 > 1 && div1 <= max_count && div2 > 1 && div2 <= max_count &&
 146            /* check for overflow */
 147            divider > div1 && divider > div2 &&
 148            divider * i8253_osc_base > divider &&
 149            divider * i8253_osc_base > i8253_osc_base) {
 150                return;
 151        }
 152
 153        divider = *nanosec / i8253_osc_base;
 154
 155        div1_lub = div2_lub = 0;
 156        div1_glb = div2_glb = 0;
 157
 158        ns_glb = 0;
 159        ns_lub = 0xffffffff;
 160
 161        div2 = max_count;
 162        start = divider / div2;
 163        if (start < 2)
 164                start = 2;
 165        for (div1 = start; div1 <= divider / div1 + 1 && div1 <= max_count;
 166             div1++) {
 167                for (div2 = divider / div1;
 168                     div1 * div2 <= divider + div1 + 1 && div2 <= max_count;
 169                     div2++) {
 170                        ns = i8253_osc_base * div1 * div2;
 171                        if (ns <= *nanosec && ns > ns_glb) {
 172                                ns_glb = ns;
 173                                div1_glb = div1;
 174                                div2_glb = div2;
 175                        }
 176                        if (ns >= *nanosec && ns < ns_lub) {
 177                                ns_lub = ns;
 178                                div1_lub = div1;
 179                                div2_lub = div2;
 180                        }
 181                }
 182        }
 183
 184        round_mode &= TRIG_ROUND_MASK;
 185        switch (round_mode) {
 186        case TRIG_ROUND_NEAREST:
 187        default:
 188                ns_high = div1_lub * div2_lub * i8253_osc_base;
 189                ns_low = div1_glb * div2_glb * i8253_osc_base;
 190                if (ns_high - *nanosec < *nanosec - ns_low) {
 191                        div1 = div1_lub;
 192                        div2 = div2_lub;
 193                } else {
 194                        div1 = div1_glb;
 195                        div2 = div2_glb;
 196                }
 197                break;
 198        case TRIG_ROUND_UP:
 199                div1 = div1_lub;
 200                div2 = div2_lub;
 201                break;
 202        case TRIG_ROUND_DOWN:
 203                div1 = div1_glb;
 204                div2 = div2_glb;
 205                break;
 206        }
 207
 208        *nanosec = div1 * div2 * i8253_osc_base;
 209        /*  masking is done since counter maps zero to 0x10000 */
 210        *d1 = div1 & 0xffff;
 211        *d2 = div2 & 0xffff;
 212        return;
 213}
 214
 215#ifndef CMDTEST
 216/* i8254_load programs 8254 counter chip.  It should also work for the 8253.
 217 * base_address is the lowest io address
 218 * for the chip (the address of counter 0).
 219 * counter_number is the counter you want to load (0,1 or 2)
 220 * count is the number to load into the counter.
 221 *
 222 * You probably want to use mode 2.
 223 *
 224 * Use i8254_mm_load() if you board uses memory-mapped io, it is
 225 * the same as i8254_load() except it uses writeb() instead of outb().
 226 *
 227 * Neither i8254_load() or i8254_read() do their loading/reading
 228 * atomically.  The 16 bit read/writes are performed with two successive
 229 * 8 bit read/writes.  So if two parts of your driver do a load/read on
 230 * the same counter, it may be necessary to protect these functions
 231 * with a spinlock.
 232 *
 233 * FMH
 234 */
 235
 236#define i8254_control_reg       3
 237
 238static inline int i8254_load(unsigned long base_address, unsigned int regshift,
 239                             unsigned int counter_number, unsigned int count,
 240                             unsigned int mode)
 241{
 242        unsigned int byte;
 243
 244        if (counter_number > 2)
 245                return -1;
 246        if (count > 0xffff)
 247                return -1;
 248        if (mode > 5)
 249                return -1;
 250        if ((mode == 2 || mode == 3) && count == 1)
 251                return -1;
 252
 253        byte = counter_number << 6;
 254        byte |= 0x30;           /*  load low then high byte */
 255        byte |= (mode << 1);    /*  set counter mode */
 256        outb(byte, base_address + (i8254_control_reg << regshift));
 257        byte = count & 0xff;    /*  lsb of counter value */
 258        outb(byte, base_address + (counter_number << regshift));
 259        byte = (count >> 8) & 0xff;     /*  msb of counter value */
 260        outb(byte, base_address + (counter_number << regshift));
 261
 262        return 0;
 263}
 264
 265static inline int i8254_mm_load(void __iomem *base_address,
 266                                unsigned int regshift,
 267                                unsigned int counter_number,
 268                                unsigned int count,
 269                                unsigned int mode)
 270{
 271        unsigned int byte;
 272
 273        if (counter_number > 2)
 274                return -1;
 275        if (count > 0xffff)
 276                return -1;
 277        if (mode > 5)
 278                return -1;
 279        if ((mode == 2 || mode == 3) && count == 1)
 280                return -1;
 281
 282        byte = counter_number << 6;
 283        byte |= 0x30;           /*  load low then high byte */
 284        byte |= (mode << 1);    /*  set counter mode */
 285        writeb(byte, base_address + (i8254_control_reg << regshift));
 286        byte = count & 0xff;    /*  lsb of counter value */
 287        writeb(byte, base_address + (counter_number << regshift));
 288        byte = (count >> 8) & 0xff;     /*  msb of counter value */
 289        writeb(byte, base_address + (counter_number << regshift));
 290
 291        return 0;
 292}
 293
 294/* Returns 16 bit counter value, should work for 8253 also.*/
 295static inline int i8254_read(unsigned long base_address, unsigned int regshift,
 296                             unsigned int counter_number)
 297{
 298        unsigned int byte;
 299        int ret;
 300
 301        if (counter_number > 2)
 302                return -1;
 303
 304        /*  latch counter */
 305        byte = counter_number << 6;
 306        outb(byte, base_address + (i8254_control_reg << regshift));
 307
 308        /*  read lsb */
 309        ret = inb(base_address + (counter_number << regshift));
 310        /*  read msb */
 311        ret += inb(base_address + (counter_number << regshift)) << 8;
 312
 313        return ret;
 314}
 315
 316static inline int i8254_mm_read(void __iomem *base_address,
 317                                unsigned int regshift,
 318                                unsigned int counter_number)
 319{
 320        unsigned int byte;
 321        int ret;
 322
 323        if (counter_number > 2)
 324                return -1;
 325
 326        /*  latch counter */
 327        byte = counter_number << 6;
 328        writeb(byte, base_address + (i8254_control_reg << regshift));
 329
 330        /*  read lsb */
 331        ret = readb(base_address + (counter_number << regshift));
 332        /*  read msb */
 333        ret += readb(base_address + (counter_number << regshift)) << 8;
 334
 335        return ret;
 336}
 337
 338/* Loads 16 bit initial counter value, should work for 8253 also. */
 339static inline void i8254_write(unsigned long base_address,
 340                               unsigned int regshift,
 341                               unsigned int counter_number, unsigned int count)
 342{
 343        unsigned int byte;
 344
 345        if (counter_number > 2)
 346                return;
 347
 348        byte = count & 0xff;    /*  lsb of counter value */
 349        outb(byte, base_address + (counter_number << regshift));
 350        byte = (count >> 8) & 0xff;     /*  msb of counter value */
 351        outb(byte, base_address + (counter_number << regshift));
 352}
 353
 354static inline void i8254_mm_write(void __iomem *base_address,
 355                                  unsigned int regshift,
 356                                  unsigned int counter_number,
 357                                  unsigned int count)
 358{
 359        unsigned int byte;
 360
 361        if (counter_number > 2)
 362                return;
 363
 364        byte = count & 0xff;    /*  lsb of counter value */
 365        writeb(byte, base_address + (counter_number << regshift));
 366        byte = (count >> 8) & 0xff;     /*  msb of counter value */
 367        writeb(byte, base_address + (counter_number << regshift));
 368}
 369
 370/* Set counter mode, should work for 8253 also.
 371 * Note: the 'mode' value is different to that for i8254_load() and comes
 372 * from the INSN_CONFIG_8254_SET_MODE command:
 373 *   I8254_MODE0, I8254_MODE1, ..., I8254_MODE5
 374 * OR'ed with:
 375 *   I8254_BCD, I8254_BINARY
 376 */
 377static inline int i8254_set_mode(unsigned long base_address,
 378                                 unsigned int regshift,
 379                                 unsigned int counter_number, unsigned int mode)
 380{
 381        unsigned int byte;
 382
 383        if (counter_number > 2)
 384                return -1;
 385        if (mode > (I8254_MODE5 | I8254_BINARY))
 386                return -1;
 387
 388        byte = counter_number << 6;
 389        byte |= 0x30;           /*  load low then high byte */
 390        byte |= mode;           /*  set counter mode and BCD|binary */
 391        outb(byte, base_address + (i8254_control_reg << regshift));
 392
 393        return 0;
 394}
 395
 396static inline int i8254_mm_set_mode(void __iomem *base_address,
 397                                    unsigned int regshift,
 398                                    unsigned int counter_number,
 399                                    unsigned int mode)
 400{
 401        unsigned int byte;
 402
 403        if (counter_number > 2)
 404                return -1;
 405        if (mode > (I8254_MODE5 | I8254_BINARY))
 406                return -1;
 407
 408        byte = counter_number << 6;
 409        byte |= 0x30;           /*  load low then high byte */
 410        byte |= mode;           /*  set counter mode and BCD|binary */
 411        writeb(byte, base_address + (i8254_control_reg << regshift));
 412
 413        return 0;
 414}
 415
 416static inline int i8254_status(unsigned long base_address,
 417                               unsigned int regshift,
 418                               unsigned int counter_number)
 419{
 420        outb(0xE0 | (2 << counter_number),
 421             base_address + (i8254_control_reg << regshift));
 422        return inb(base_address + (counter_number << regshift));
 423}
 424
 425static inline int i8254_mm_status(void __iomem *base_address,
 426                                  unsigned int regshift,
 427                                  unsigned int counter_number)
 428{
 429        writeb(0xE0 | (2 << counter_number),
 430               base_address + (i8254_control_reg << regshift));
 431        return readb(base_address + (counter_number << regshift));
 432}
 433
 434#endif
 435
 436#endif
 437