OpenCores
URL https://opencores.org/ocsvn/test_project/test_project/trunk

Subversion Repositories test_project

[/] [test_project/] [trunk/] [linux_sd_driver/] [drivers/] [hwmon/] [hdaps.c] - Blame information for rev 62

Details | Compare with Previous | View Log

Line No. Rev Author Line
1 62 marcus.erl
/*
2
 * drivers/hwmon/hdaps.c - driver for IBM's Hard Drive Active Protection System
3
 *
4
 * Copyright (C) 2005 Robert Love <rml@novell.com>
5
 * Copyright (C) 2005 Jesper Juhl <jesper.juhl@gmail.com>
6
 *
7
 * The HardDisk Active Protection System (hdaps) is present in IBM ThinkPads
8
 * starting with the R40, T41, and X40.  It provides a basic two-axis
9
 * accelerometer and other data, such as the device's temperature.
10
 *
11
 * This driver is based on the document by Mark A. Smith available at
12
 * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html and a lot of trial
13
 * and error.
14
 *
15
 * This program is free software; you can redistribute it and/or modify it
16
 * under the terms of the GNU General Public License v2 as published by the
17
 * Free Software Foundation.
18
 *
19
 * This program is distributed in the hope that it will be useful, but WITHOUT
20
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
22
 * more details.
23
 *
24
 * You should have received a copy of the GNU General Public License along with
25
 * this program; if not, write to the Free Software Foundation, Inc.,
26
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
27
 */
28
 
29
#include <linux/delay.h>
30
#include <linux/platform_device.h>
31
#include <linux/input-polldev.h>
32
#include <linux/kernel.h>
33
#include <linux/mutex.h>
34
#include <linux/module.h>
35
#include <linux/timer.h>
36
#include <linux/dmi.h>
37
#include <linux/jiffies.h>
38
 
39
#include <asm/io.h>
40
 
41
#define HDAPS_LOW_PORT          0x1600  /* first port used by hdaps */
42
#define HDAPS_NR_PORTS          0x30    /* number of ports: 0x1600 - 0x162f */
43
 
44
#define HDAPS_PORT_STATE        0x1611  /* device state */
45
#define HDAPS_PORT_YPOS         0x1612  /* y-axis position */
46
#define HDAPS_PORT_XPOS         0x1614  /* x-axis position */
47
#define HDAPS_PORT_TEMP1        0x1616  /* device temperature, in Celsius */
48
#define HDAPS_PORT_YVAR         0x1617  /* y-axis variance (what is this?) */
49
#define HDAPS_PORT_XVAR         0x1619  /* x-axis variance (what is this?) */
50
#define HDAPS_PORT_TEMP2        0x161b  /* device temperature (again?) */
51
#define HDAPS_PORT_UNKNOWN      0x161c  /* what is this? */
52
#define HDAPS_PORT_KMACT        0x161d  /* keyboard or mouse activity */
53
 
54
#define STATE_FRESH             0x50    /* accelerometer data is fresh */
55
 
56
#define KEYBD_MASK              0x20    /* set if keyboard activity */
57
#define MOUSE_MASK              0x40    /* set if mouse activity */
58
#define KEYBD_ISSET(n)          (!! (n & KEYBD_MASK))   /* keyboard used? */
59
#define MOUSE_ISSET(n)          (!! (n & MOUSE_MASK))   /* mouse used? */
60
 
61
#define INIT_TIMEOUT_MSECS      4000    /* wait up to 4s for device init ... */
62
#define INIT_WAIT_MSECS         200     /* ... in 200ms increments */
63
 
64
#define HDAPS_POLL_INTERVAL     50      /* poll for input every 1/20s (50 ms)*/
65
#define HDAPS_INPUT_FUZZ        4       /* input event threshold */
66
#define HDAPS_INPUT_FLAT        4
67
 
68
static struct platform_device *pdev;
69
static struct input_polled_dev *hdaps_idev;
70
static unsigned int hdaps_invert;
71
static u8 km_activity;
72
static int rest_x;
73
static int rest_y;
74
 
75
static DEFINE_MUTEX(hdaps_mtx);
76
 
77
/*
78
 * __get_latch - Get the value from a given port.  Callers must hold hdaps_mtx.
79
 */
80
static inline u8 __get_latch(u16 port)
81
{
82
        return inb(port) & 0xff;
83
}
84
 
85
/*
86
 * __check_latch - Check a port latch for a given value.  Returns zero if the
87
 * port contains the given value.  Callers must hold hdaps_mtx.
88
 */
89
static inline int __check_latch(u16 port, u8 val)
90
{
91
        if (__get_latch(port) == val)
92
                return 0;
93
        return -EINVAL;
94
}
95
 
96
/*
97
 * __wait_latch - Wait up to 100us for a port latch to get a certain value,
98
 * returning zero if the value is obtained.  Callers must hold hdaps_mtx.
99
 */
100
static int __wait_latch(u16 port, u8 val)
101
{
102
        unsigned int i;
103
 
104
        for (i = 0; i < 20; i++) {
105
                if (!__check_latch(port, val))
106
                        return 0;
107
                udelay(5);
108
        }
109
 
110
        return -EIO;
111
}
112
 
113
/*
114
 * __device_refresh - request a refresh from the accelerometer.  Does not wait
115
 * for refresh to complete.  Callers must hold hdaps_mtx.
116
 */
117
static void __device_refresh(void)
118
{
119
        udelay(200);
120
        if (inb(0x1604) != STATE_FRESH) {
121
                outb(0x11, 0x1610);
122
                outb(0x01, 0x161f);
123
        }
124
}
125
 
126
/*
127
 * __device_refresh_sync - request a synchronous refresh from the
128
 * accelerometer.  We wait for the refresh to complete.  Returns zero if
129
 * successful and nonzero on error.  Callers must hold hdaps_mtx.
130
 */
131
static int __device_refresh_sync(void)
132
{
133
        __device_refresh();
134
        return __wait_latch(0x1604, STATE_FRESH);
135
}
136
 
137
/*
138
 * __device_complete - indicate to the accelerometer that we are done reading
139
 * data, and then initiate an async refresh.  Callers must hold hdaps_mtx.
140
 */
141
static inline void __device_complete(void)
142
{
143
        inb(0x161f);
144
        inb(0x1604);
145
        __device_refresh();
146
}
147
 
148
/*
149
 * hdaps_readb_one - reads a byte from a single I/O port, placing the value in
150
 * the given pointer.  Returns zero on success or a negative error on failure.
151
 * Can sleep.
152
 */
153
static int hdaps_readb_one(unsigned int port, u8 *val)
154
{
155
        int ret;
156
 
157
        mutex_lock(&hdaps_mtx);
158
 
159
        /* do a sync refresh -- we need to be sure that we read fresh data */
160
        ret = __device_refresh_sync();
161
        if (ret)
162
                goto out;
163
 
164
        *val = inb(port);
165
        __device_complete();
166
 
167
out:
168
        mutex_unlock(&hdaps_mtx);
169
        return ret;
170
}
171
 
172
/* __hdaps_read_pair - internal lockless helper for hdaps_read_pair(). */
173
static int __hdaps_read_pair(unsigned int port1, unsigned int port2,
174
                             int *x, int *y)
175
{
176
        /* do a sync refresh -- we need to be sure that we read fresh data */
177
        if (__device_refresh_sync())
178
                return -EIO;
179
 
180
        *y = inw(port2);
181
        *x = inw(port1);
182
        km_activity = inb(HDAPS_PORT_KMACT);
183
        __device_complete();
184
 
185
        /* if hdaps_invert is set, negate the two values */
186
        if (hdaps_invert) {
187
                *x = -*x;
188
                *y = -*y;
189
        }
190
 
191
        return 0;
192
}
193
 
194
/*
195
 * hdaps_read_pair - reads the values from a pair of ports, placing the values
196
 * in the given pointers.  Returns zero on success.  Can sleep.
197
 */
198
static int hdaps_read_pair(unsigned int port1, unsigned int port2,
199
                           int *val1, int *val2)
200
{
201
        int ret;
202
 
203
        mutex_lock(&hdaps_mtx);
204
        ret = __hdaps_read_pair(port1, port2, val1, val2);
205
        mutex_unlock(&hdaps_mtx);
206
 
207
        return ret;
208
}
209
 
210
/*
211
 * hdaps_device_init - initialize the accelerometer.  Returns zero on success
212
 * and negative error code on failure.  Can sleep.
213
 */
214
static int hdaps_device_init(void)
215
{
216
        int total, ret = -ENXIO;
217
 
218
        mutex_lock(&hdaps_mtx);
219
 
220
        outb(0x13, 0x1610);
221
        outb(0x01, 0x161f);
222
        if (__wait_latch(0x161f, 0x00))
223
                goto out;
224
 
225
        /*
226
         * Most ThinkPads return 0x01.
227
         *
228
         * Others--namely the R50p, T41p, and T42p--return 0x03.  These laptops
229
         * have "inverted" axises.
230
         *
231
         * The 0x02 value occurs when the chip has been previously initialized.
232
         */
233
        if (__check_latch(0x1611, 0x03) &&
234
                     __check_latch(0x1611, 0x02) &&
235
                     __check_latch(0x1611, 0x01))
236
                goto out;
237
 
238
        printk(KERN_DEBUG "hdaps: initial latch check good (0x%02x).\n",
239
               __get_latch(0x1611));
240
 
241
        outb(0x17, 0x1610);
242
        outb(0x81, 0x1611);
243
        outb(0x01, 0x161f);
244
        if (__wait_latch(0x161f, 0x00))
245
                goto out;
246
        if (__wait_latch(0x1611, 0x00))
247
                goto out;
248
        if (__wait_latch(0x1612, 0x60))
249
                goto out;
250
        if (__wait_latch(0x1613, 0x00))
251
                goto out;
252
        outb(0x14, 0x1610);
253
        outb(0x01, 0x1611);
254
        outb(0x01, 0x161f);
255
        if (__wait_latch(0x161f, 0x00))
256
                goto out;
257
        outb(0x10, 0x1610);
258
        outb(0xc8, 0x1611);
259
        outb(0x00, 0x1612);
260
        outb(0x02, 0x1613);
261
        outb(0x01, 0x161f);
262
        if (__wait_latch(0x161f, 0x00))
263
                goto out;
264
        if (__device_refresh_sync())
265
                goto out;
266
        if (__wait_latch(0x1611, 0x00))
267
                goto out;
268
 
269
        /* we have done our dance, now let's wait for the applause */
270
        for (total = INIT_TIMEOUT_MSECS; total > 0; total -= INIT_WAIT_MSECS) {
271
                int x, y;
272
 
273
                /* a read of the device helps push it into action */
274
                __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
275
                if (!__wait_latch(0x1611, 0x02)) {
276
                        ret = 0;
277
                        break;
278
                }
279
 
280
                msleep(INIT_WAIT_MSECS);
281
        }
282
 
283
out:
284
        mutex_unlock(&hdaps_mtx);
285
        return ret;
286
}
287
 
288
 
289
/* Device model stuff */
290
 
291
static int hdaps_probe(struct platform_device *dev)
292
{
293
        int ret;
294
 
295
        ret = hdaps_device_init();
296
        if (ret)
297
                return ret;
298
 
299
        printk(KERN_INFO "hdaps: device successfully initialized.\n");
300
        return 0;
301
}
302
 
303
static int hdaps_resume(struct platform_device *dev)
304
{
305
        return hdaps_device_init();
306
}
307
 
308
static struct platform_driver hdaps_driver = {
309
        .probe = hdaps_probe,
310
        .resume = hdaps_resume,
311
        .driver = {
312
                .name = "hdaps",
313
                .owner = THIS_MODULE,
314
        },
315
};
316
 
317
/*
318
 * hdaps_calibrate - Set our "resting" values.  Callers must hold hdaps_mtx.
319
 */
320
static void hdaps_calibrate(void)
321
{
322
        __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y);
323
}
324
 
325
static void hdaps_mousedev_poll(struct input_polled_dev *dev)
326
{
327
        struct input_dev *input_dev = dev->input;
328
        int x, y;
329
 
330
        mutex_lock(&hdaps_mtx);
331
 
332
        if (__hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y))
333
                goto out;
334
 
335
        input_report_abs(input_dev, ABS_X, x - rest_x);
336
        input_report_abs(input_dev, ABS_Y, y - rest_y);
337
        input_sync(input_dev);
338
 
339
out:
340
        mutex_unlock(&hdaps_mtx);
341
}
342
 
343
 
344
/* Sysfs Files */
345
 
346
static ssize_t hdaps_position_show(struct device *dev,
347
                                   struct device_attribute *attr, char *buf)
348
{
349
        int ret, x, y;
350
 
351
        ret = hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &x, &y);
352
        if (ret)
353
                return ret;
354
 
355
        return sprintf(buf, "(%d,%d)\n", x, y);
356
}
357
 
358
static ssize_t hdaps_variance_show(struct device *dev,
359
                                   struct device_attribute *attr, char *buf)
360
{
361
        int ret, x, y;
362
 
363
        ret = hdaps_read_pair(HDAPS_PORT_XVAR, HDAPS_PORT_YVAR, &x, &y);
364
        if (ret)
365
                return ret;
366
 
367
        return sprintf(buf, "(%d,%d)\n", x, y);
368
}
369
 
370
static ssize_t hdaps_temp1_show(struct device *dev,
371
                                struct device_attribute *attr, char *buf)
372
{
373
        u8 temp;
374
        int ret;
375
 
376
        ret = hdaps_readb_one(HDAPS_PORT_TEMP1, &temp);
377
        if (ret < 0)
378
                return ret;
379
 
380
        return sprintf(buf, "%u\n", temp);
381
}
382
 
383
static ssize_t hdaps_temp2_show(struct device *dev,
384
                                struct device_attribute *attr, char *buf)
385
{
386
        u8 temp;
387
        int ret;
388
 
389
        ret = hdaps_readb_one(HDAPS_PORT_TEMP2, &temp);
390
        if (ret < 0)
391
                return ret;
392
 
393
        return sprintf(buf, "%u\n", temp);
394
}
395
 
396
static ssize_t hdaps_keyboard_activity_show(struct device *dev,
397
                                            struct device_attribute *attr,
398
                                            char *buf)
399
{
400
        return sprintf(buf, "%u\n", KEYBD_ISSET(km_activity));
401
}
402
 
403
static ssize_t hdaps_mouse_activity_show(struct device *dev,
404
                                         struct device_attribute *attr,
405
                                         char *buf)
406
{
407
        return sprintf(buf, "%u\n", MOUSE_ISSET(km_activity));
408
}
409
 
410
static ssize_t hdaps_calibrate_show(struct device *dev,
411
                                    struct device_attribute *attr, char *buf)
412
{
413
        return sprintf(buf, "(%d,%d)\n", rest_x, rest_y);
414
}
415
 
416
static ssize_t hdaps_calibrate_store(struct device *dev,
417
                                     struct device_attribute *attr,
418
                                     const char *buf, size_t count)
419
{
420
        mutex_lock(&hdaps_mtx);
421
        hdaps_calibrate();
422
        mutex_unlock(&hdaps_mtx);
423
 
424
        return count;
425
}
426
 
427
static ssize_t hdaps_invert_show(struct device *dev,
428
                                 struct device_attribute *attr, char *buf)
429
{
430
        return sprintf(buf, "%u\n", hdaps_invert);
431
}
432
 
433
static ssize_t hdaps_invert_store(struct device *dev,
434
                                  struct device_attribute *attr,
435
                                  const char *buf, size_t count)
436
{
437
        int invert;
438
 
439
        if (sscanf(buf, "%d", &invert) != 1 || (invert != 1 && invert != 0))
440
                return -EINVAL;
441
 
442
        hdaps_invert = invert;
443
        hdaps_calibrate();
444
 
445
        return count;
446
}
447
 
448
static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL);
449
static DEVICE_ATTR(variance, 0444, hdaps_variance_show, NULL);
450
static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL);
451
static DEVICE_ATTR(temp2, 0444, hdaps_temp2_show, NULL);
452
static DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL);
453
static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL);
454
static DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show,hdaps_calibrate_store);
455
static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store);
456
 
457
static struct attribute *hdaps_attributes[] = {
458
        &dev_attr_position.attr,
459
        &dev_attr_variance.attr,
460
        &dev_attr_temp1.attr,
461
        &dev_attr_temp2.attr,
462
        &dev_attr_keyboard_activity.attr,
463
        &dev_attr_mouse_activity.attr,
464
        &dev_attr_calibrate.attr,
465
        &dev_attr_invert.attr,
466
        NULL,
467
};
468
 
469
static struct attribute_group hdaps_attribute_group = {
470
        .attrs = hdaps_attributes,
471
};
472
 
473
 
474
/* Module stuff */
475
 
476
/* hdaps_dmi_match - found a match.  return one, short-circuiting the hunt. */
477
static int __init hdaps_dmi_match(const struct dmi_system_id *id)
478
{
479
        printk(KERN_INFO "hdaps: %s detected.\n", id->ident);
480
        return 1;
481
}
482
 
483
/* hdaps_dmi_match_invert - found an inverted match. */
484
static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id)
485
{
486
        hdaps_invert = 1;
487
        printk(KERN_INFO "hdaps: inverting axis readings.\n");
488
        return hdaps_dmi_match(id);
489
}
490
 
491
#define HDAPS_DMI_MATCH_NORMAL(vendor, model) {         \
492
        .ident = vendor " " model,                      \
493
        .callback = hdaps_dmi_match,                    \
494
        .matches = {                                    \
495
                DMI_MATCH(DMI_BOARD_VENDOR, vendor),    \
496
                DMI_MATCH(DMI_PRODUCT_VERSION, model)   \
497
        }                                               \
498
}
499
 
500
#define HDAPS_DMI_MATCH_INVERT(vendor, model) {         \
501
        .ident = vendor " " model,                      \
502
        .callback = hdaps_dmi_match_invert,             \
503
        .matches = {                                    \
504
                DMI_MATCH(DMI_BOARD_VENDOR, vendor),    \
505
                DMI_MATCH(DMI_PRODUCT_VERSION, model)   \
506
        }                                               \
507
}
508
 
509
/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
510
   "ThinkPad T42p", so the order of the entries matters.
511
   If your ThinkPad is not recognized, please update to latest
512
   BIOS. This is especially the case for some R52 ThinkPads. */
513
static struct dmi_system_id __initdata hdaps_whitelist[] = {
514
        HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p"),
515
        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
516
        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
517
        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
518
        HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p"),
519
        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
520
        HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p"),
521
        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
522
        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
523
        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60"),
524
        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
525
        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X41"),
526
        HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60"),
527
        HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
528
        { .ident = NULL }
529
};
530
 
531
static int __init hdaps_init(void)
532
{
533
        struct input_dev *idev;
534
        int ret;
535
 
536
        if (!dmi_check_system(hdaps_whitelist)) {
537
                printk(KERN_WARNING "hdaps: supported laptop not found!\n");
538
                ret = -ENODEV;
539
                goto out;
540
        }
541
 
542
        if (!request_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS, "hdaps")) {
543
                ret = -ENXIO;
544
                goto out;
545
        }
546
 
547
        ret = platform_driver_register(&hdaps_driver);
548
        if (ret)
549
                goto out_region;
550
 
551
        pdev = platform_device_register_simple("hdaps", -1, NULL, 0);
552
        if (IS_ERR(pdev)) {
553
                ret = PTR_ERR(pdev);
554
                goto out_driver;
555
        }
556
 
557
        ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group);
558
        if (ret)
559
                goto out_device;
560
 
561
        hdaps_idev = input_allocate_polled_device();
562
        if (!hdaps_idev) {
563
                ret = -ENOMEM;
564
                goto out_group;
565
        }
566
 
567
        hdaps_idev->poll = hdaps_mousedev_poll;
568
        hdaps_idev->poll_interval = HDAPS_POLL_INTERVAL;
569
 
570
        /* initial calibrate for the input device */
571
        hdaps_calibrate();
572
 
573
        /* initialize the input class */
574
        idev = hdaps_idev->input;
575
        idev->name = "hdaps";
576
        idev->dev.parent = &pdev->dev;
577
        idev->evbit[0] = BIT_MASK(EV_ABS);
578
        input_set_abs_params(idev, ABS_X,
579
                        -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
580
        input_set_abs_params(idev, ABS_Y,
581
                        -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT);
582
 
583
        ret = input_register_polled_device(hdaps_idev);
584
        if (ret)
585
                goto out_idev;
586
 
587
        printk(KERN_INFO "hdaps: driver successfully loaded.\n");
588
        return 0;
589
 
590
out_idev:
591
        input_free_polled_device(hdaps_idev);
592
out_group:
593
        sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
594
out_device:
595
        platform_device_unregister(pdev);
596
out_driver:
597
        platform_driver_unregister(&hdaps_driver);
598
out_region:
599
        release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
600
out:
601
        printk(KERN_WARNING "hdaps: driver init failed (ret=%d)!\n", ret);
602
        return ret;
603
}
604
 
605
static void __exit hdaps_exit(void)
606
{
607
        input_unregister_polled_device(hdaps_idev);
608
        input_free_polled_device(hdaps_idev);
609
        sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group);
610
        platform_device_unregister(pdev);
611
        platform_driver_unregister(&hdaps_driver);
612
        release_region(HDAPS_LOW_PORT, HDAPS_NR_PORTS);
613
 
614
        printk(KERN_INFO "hdaps: driver unloaded.\n");
615
}
616
 
617
module_init(hdaps_init);
618
module_exit(hdaps_exit);
619
 
620
module_param_named(invert, hdaps_invert, bool, 0);
621
MODULE_PARM_DESC(invert, "invert data along each axis");
622
 
623
MODULE_AUTHOR("Robert Love");
624
MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver");
625
MODULE_LICENSE("GPL v2");

powered by: WebSVN 2.1.0

© copyright 1999-2025 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.