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        *d1 = div1 & 0xffff;    /*  masking is done since counter maps zero to 0x10000 */
 210        *d2 = div2 & 0xffff;
 211        return;
 212}
 213
 214#ifndef CMDTEST
 215/* i8254_load programs 8254 counter chip.  It should also work for the 8253.
 216 * base_address is the lowest io address for the chip (the address of counter 0).
 217 * counter_number is the counter you want to load (0,1 or 2)
 218 * count is the number to load into the counter.
 219 *
 220 * You probably want to use mode 2.
 221 *
 222 * Use i8254_mm_load() if you board uses memory-mapped io, it is
 223 * the same as i8254_load() except it uses writeb() instead of outb().
 224 *
 225 * Neither i8254_load() or i8254_read() do their loading/reading
 226 * atomically.  The 16 bit read/writes are performed with two successive
 227 * 8 bit read/writes.  So if two parts of your driver do a load/read on
 228 * the same counter, it may be necessary to protect these functions
 229 * with a spinlock.
 230 *
 231 * FMH
 232 */
 233
 234#define i8254_control_reg       3
 235
 236static inline int i8254_load(unsigned long base_address, unsigned int regshift,
 237                             unsigned int counter_number, unsigned int count,
 238                             unsigned int mode)
 239{
 240        unsigned int byte;
 241
 242        if (counter_number > 2)
 243                return -1;
 244        if (count > 0xffff)
 245                return -1;
 246        if (mode > 5)
 247                return -1;
 248        if ((mode == 2 || mode == 3) && count == 1)
 249                return -1;
 250
 251        byte = counter_number << 6;
 252        byte |= 0x30;           /*  load low then high byte */
 253        byte |= (mode << 1);    /*  set counter mode */
 254        outb(byte, base_address + (i8254_control_reg << regshift));
 255        byte = count & 0xff;    /*  lsb of counter value */
 256        outb(byte, base_address + (counter_number << regshift));
 257        byte = (count >> 8) & 0xff;     /*  msb of counter value */
 258        outb(byte, base_address + (counter_number << regshift));
 259
 260        return 0;
 261}
 262
 263static inline int i8254_mm_load(void *base_address, unsigned int regshift,
 264                                unsigned int counter_number, unsigned int count,
 265                                unsigned int mode)
 266{
 267        unsigned int byte;
 268
 269        if (counter_number > 2)
 270                return -1;
 271        if (count > 0xffff)
 272                return -1;
 273        if (mode > 5)
 274                return -1;
 275        if ((mode == 2 || mode == 3) && count == 1)
 276                return -1;
 277
 278        byte = counter_number << 6;
 279        byte |= 0x30;           /*  load low then high byte */
 280        byte |= (mode << 1);    /*  set counter mode */
 281        writeb(byte, base_address + (i8254_control_reg << regshift));
 282        byte = count & 0xff;    /*  lsb of counter value */
 283        writeb(byte, base_address + (counter_number << regshift));
 284        byte = (count >> 8) & 0xff;     /*  msb of counter value */
 285        writeb(byte, base_address + (counter_number << regshift));
 286
 287        return 0;
 288}
 289
 290/* Returns 16 bit counter value, should work for 8253 also.*/
 291static inline int i8254_read(unsigned long base_address, unsigned int regshift,
 292                             unsigned int counter_number)
 293{
 294        unsigned int byte;
 295        int ret;
 296
 297        if (counter_number > 2)
 298                return -1;
 299
 300        /*  latch counter */
 301        byte = counter_number << 6;
 302        outb(byte, base_address + (i8254_control_reg << regshift));
 303
 304        /*  read lsb */
 305        ret = inb(base_address + (counter_number << regshift));
 306        /*  read msb */
 307        ret += inb(base_address + (counter_number << regshift)) << 8;
 308
 309        return ret;
 310}
 311
 312static inline int i8254_mm_read(void *base_address, unsigned int regshift,
 313                                unsigned int counter_number)
 314{
 315        unsigned int byte;
 316        int ret;
 317
 318        if (counter_number > 2)
 319                return -1;
 320
 321        /*  latch counter */
 322        byte = counter_number << 6;
 323        writeb(byte, base_address + (i8254_control_reg << regshift));
 324
 325        /*  read lsb */
 326        ret = readb(base_address + (counter_number << regshift));
 327        /*  read msb */
 328        ret += readb(base_address + (counter_number << regshift)) << 8;
 329
 330        return ret;
 331}
 332
 333/* Loads 16 bit initial counter value, should work for 8253 also. */
 334static inline void i8254_write(unsigned long base_address,
 335                               unsigned int regshift,
 336                               unsigned int counter_number, unsigned int count)
 337{
 338        unsigned int byte;
 339
 340        if (counter_number > 2)
 341                return;
 342
 343        byte = count & 0xff;    /*  lsb of counter value */
 344        outb(byte, base_address + (counter_number << regshift));
 345        byte = (count >> 8) & 0xff;     /*  msb of counter value */
 346        outb(byte, base_address + (counter_number << regshift));
 347}
 348
 349static inline void i8254_mm_write(void *base_address,
 350                                  unsigned int regshift,
 351                                  unsigned int counter_number,
 352                                  unsigned int count)
 353{
 354        unsigned int byte;
 355
 356        if (counter_number > 2)
 357                return;
 358
 359        byte = count & 0xff;    /*  lsb of counter value */
 360        writeb(byte, base_address + (counter_number << regshift));
 361        byte = (count >> 8) & 0xff;     /*  msb of counter value */
 362        writeb(byte, base_address + (counter_number << regshift));
 363}
 364
 365/* Set counter mode, should work for 8253 also.
 366 * Note: the 'mode' value is different to that for i8254_load() and comes
 367 * from the INSN_CONFIG_8254_SET_MODE command:
 368 *   I8254_MODE0, I8254_MODE1, ..., I8254_MODE5
 369 * OR'ed with:
 370 *   I8254_BCD, I8254_BINARY
 371 */
 372static inline int i8254_set_mode(unsigned long base_address,
 373                                 unsigned int regshift,
 374                                 unsigned int counter_number, unsigned int mode)
 375{
 376        unsigned int byte;
 377
 378        if (counter_number > 2)
 379                return -1;
 380        if (mode > (I8254_MODE5 | I8254_BINARY))
 381                return -1;
 382
 383        byte = counter_number << 6;
 384        byte |= 0x30;           /*  load low then high byte */
 385        byte |= mode;           /*  set counter mode and BCD|binary */
 386        outb(byte, base_address + (i8254_control_reg << regshift));
 387
 388        return 0;
 389}
 390
 391static inline int i8254_mm_set_mode(void *base_address,
 392                                    unsigned int regshift,
 393                                    unsigned int counter_number,
 394                                    unsigned int mode)
 395{
 396        unsigned int byte;
 397
 398        if (counter_number > 2)
 399                return -1;
 400        if (mode > (I8254_MODE5 | I8254_BINARY))
 401                return -1;
 402
 403        byte = counter_number << 6;
 404        byte |= 0x30;           /*  load low then high byte */
 405        byte |= mode;           /*  set counter mode and BCD|binary */
 406        writeb(byte, base_address + (i8254_control_reg << regshift));
 407
 408        return 0;
 409}
 410
 411static inline int i8254_status(unsigned long base_address,
 412                               unsigned int regshift,
 413                               unsigned int counter_number)
 414{
 415        outb(0xE0 | (2 << counter_number),
 416             base_address + (i8254_control_reg << regshift));
 417        return inb(base_address + (counter_number << regshift));
 418}
 419
 420static inline int i8254_mm_status(void *base_address,
 421                                  unsigned int regshift,
 422                                  unsigned int counter_number)
 423{
 424        writeb(0xE0 | (2 << counter_number),
 425               base_address + (i8254_control_reg << regshift));
 426        return readb(base_address + (counter_number << regshift));
 427}
 428
 429#endif
 430
 431#endif
 432