filei8253 PIT をアクセスするデバイスドライバ/i8253_ref.c
Expand allFold all
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
 
 
 
 
 
 
 
 
 
 
 
-
|
|
!
-
|
|
|
|
|
|
!
 
-
|
|
|
!
 
-
|
|
|
|
|
|
|
|
|
|
|
|
!
 
-
|
|
|
!
 
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
 
-
|
|
|
|
|
|
|
!
 
 
-
|
|
-
|
|
!
|
!
 
-
|
|
|
|
|
|
|
!
 
 
-
|
|
-
|
|
!
-
|
|
!
|
!
 
-
|
|
|
|
|
|
|
|
!
 
 
 
-
|
|
|
|
-
|
|
!
|
|
-
|
|
!
-
|
|
!
-
|
|
!
|
|
|
!
 
-
|
|
!
 
 
 
-
|
|
!
-
|
|
|
!
 
-
|
!
-
|
!
 
 
 
-
|
|
|
|
!
 
-
|
|
|
-
|
|
|
!
|
-
|
|
!
|
|
|
|
|
!
 
-
|
|
|
|
!
 
-
|
|
|
-
|
|
|
!
|
-
|
|
!
|
|
|
|
!
 
 
 
 
 
 
 
 
 
-
|
|
|
|
!
 
-
|
|
|
|
|
|
|
|
|
|
|
-
|
|
|
!
|
-
|
|
|
|
|
!
-
|
!
|
|
|
-
|
|
|
|
|
!
-
|
!
|
|
|
|
-
|
|
|
!
|
|
-
|
!
|
|
|
-
|
|
|
!
|
|
|
|
|
|
|
|
|
|
!
 
-
|
|
|
|
!
 
-
|
|
|
|
|
|
|
|
|
-
|
|
|
!
|
-
|
!
|
|
|
|
|
|
!
 
-
|
|
!
 
-
|
|
|
!
 
-
|
!
 
-
|
|
!
 
 
-
|
!
-
-
|
|
|
|
!
|
|
|
!
 
-
|
|
|
!
 
-
|
|
-
|
|
|
|
!
|
!
 
-
|
|
!
 
-
|
|
!
 
 
 
 
 
 
 
/*
 * i8253_ref.c - I8253 PIT DRAM refresh counter platform driver.
 *
 * Copyright (c) 2017 Akinori Furuta <afuruta@m7.dion.ne.jp>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 */
 
#include <linux/kernel.h>
#include <linux/printk.h>
#include <linux/spinlock.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/i8253.h>
#include <linux/i8253_control.h>
#include <linux/i8253_ref.h>
 
/*
 * i8253 PIT Refresh counter driver state.
 *
 */
struct i8253_ref {
        struct platform_device  *pdev;
        unsigned long   refresh_counter;        /*!< i8253 data port assigned to Refresh Timer. */
        unsigned long   control_word;           /*!< i8253 control word port. */
        uint8_t         channel;                /*!< i8253 channel assigned to Refresh Timer. */
        uint32_t        rate_default;           /*!< i8253 divider ratio. */
        unsigned long   rate_saved;             /*!< i8253 divider ratio read back value. */
};
 
/*
 *  Read i8253 counter.
 *  @param  iref points driver state.
 *  @return uint16_t counter value.
 */
uint16_t i8253_ref_counter_read(struct i8253_ref *iref)
{       unsigned long   flags;
        uint16_t        counter;
 
        raw_spin_lock_irqsave(&i8253_lock, flags);
        outb_p(   I8253_CONTROL_WORD_SCX_CH(iref->channel)
                | I8253_CONTROL_WORD_RLX_LATCH
                | I8253_CONTROL_WORD_MX_RATE_GENERATOR
                , iref->control_word
        );
        counter =  (inb_p(iref->refresh_counter)) << 0x0;
        counter |= (inb(iref->refresh_counter)) << 0x8;
        raw_spin_unlock_irqrestore(&i8253_lock, flags);
        return counter;
}
 
/*
 *  Write i8253 counter.
 *  @param iref points driver state.
 *  @param rate Refresh counter divider rate.
 */
void i8253_ref_rate_write(struct i8253_ref *iref, uint16_t rate)
{       unsigned long   flags;
 
        raw_spin_lock_irqsave(&i8253_lock, flags);
        outb_p(   I8253_CONTROL_WORD_SCX_CH(iref->channel)
                | I8253_CONTROL_WORD_RLX_LSB_MSB
                | I8253_CONTROL_WORD_MX_RATE_GENERATOR
                , iref->control_word
        );
        outb_p((rate >> 0x0) & 0xff
                , iref->refresh_counter
        );
        outb((rate >> 0x8) & 0xff
                , iref->refresh_counter
        );
        raw_spin_unlock_irqrestore(&i8253_lock, flags);
}
 
/*
 *  counter node: show (user does read())
 *  @param dev points device context.
 *  @param attr points device attribute node.
 *  @param buf points buffer to store read stream.
 *  @return ssize_t >=0: bytes in buffer, \
 *                  <0: error, negative errno number.
 *  @note fs/kernfs/sysfs allocates buffer which length is PAGE_SIZE.
 */
ssize_t i8253_ref_counter_show(struct device *dev,
        struct device_attribute *attr, char *buf)
{       struct i8253_ref        *iref;
 
        iref = dev_get_drvdata(dev);
        if (!iref) {
                printk(KERN_ERR "%s: Driver data gnoe away.\n", __func__);
                return  -ENODEV;
        }
        return snprintf(buf, PAGE_SIZE, "%u\n", (unsigned)i8253_ref_counter_read(iref));
}
 
/*
 *  rate node: show (user does read())
 *  @param dev points device context.
 *  @param attr points device attribute node.
 *  @param buf points buffer to store read stream.
 *  @return ssize_t >=0: bytes in buffer, \
 *                  <0: error, negative errno number.
 *  @note fs/kernfs/sysfs allocates buffer which length is PAGE_SIZE.
 */
ssize_t i8253_ref_rate_show(struct device *dev,
        struct device_attribute *attr, char *buf)
{       struct i8253_ref        *iref;
 
        iref = dev_get_drvdata(dev);
        if (!iref) {
                printk(KERN_ERR "%s: Driver data gnoe away.\n", __func__);
                return  -ENODEV;
        }
        if (iref->rate_saved == I8253_REF_RATE_DEFAULT_KEEP) {
                /* Read back value is not available. */
                return snprintf(buf, PAGE_SIZE, "-1\n");
        }
        return snprintf(buf, PAGE_SIZE, "%lu\n", (unsigned long)(iref->rate_saved));
}
 
/*
 *  rate node: store (user does write())
 *  @param dev points device context.
 *  @param attr points device attribute node.
 *  @param buf points buffer filled with write stream.
 *  @param count write stream bytes in buffer.
 *  @return ssize_t >=0: bytes read from buffer, \
 *                  <0: error, negative errno number.
 *  @note kernfs/sysfs terminates write stream by '\0'.
 */
ssize_t i8253_ref_rate_store(struct device *dev,
        struct device_attribute *attr,
        const char *buf, size_t count)
{       char                    *p2;
        unsigned long           rate;
        struct i8253_ref        *iref;
 
        iref = dev_get_drvdata(dev);
        if (!iref) {
                printk(KERN_ERR "%s: Driver data gnoe away.\n", __func__);
                return  -ENODEV;
        }
        p2 = NULL;
        rate = simple_strtoul(buf,&p2,0);
        if (!p2) {
                /* simple_strtoul doesn't work. */
                return -EINVAL;
        }
        if ((unsigned)(*p2) >= ' ') {
                /* Terminated with some invalid character. */
                return -EINVAL;
        }
        if (rate > 0xffff) {
                /* Too large value. */
                return -EINVAL;
        }
        i8253_ref_rate_write(iref, rate);
        iref->rate_saved = rate;
        return (__force ssize_t)count;
}
 
/*
 * Define device attributes.
 * device attribute appears as sysfs node in device directory.
 */
DEVICE_ATTR(counter, S_IRUGO, i8253_ref_counter_show, NULL);
DEVICE_ATTR(rate,    S_IWUSR | S_IRUGO, i8253_ref_rate_show, i8253_ref_rate_store);
 
/*
 *  Device attribute entries.
 *  @note DEVICE_ATTR creates structure dev_attr_##name
 */
static struct attribute *i8253_ref_attrs[] = {
        &dev_attr_counter.attr,
        &dev_attr_rate.attr,
        NULL,
};
 
/*
 * Device attribute group.
 */
static struct attribute_group i8253_ref_group = {
        .attrs = i8253_ref_attrs,
};
 
#if (defined(CONFIG_PM))
/* Power management is enabled. */
/*
 * Handle event suspend.
 * @param dev points struct device.
 * @return int ==0: Success \
 *             <0:  Fail, Negative errno number.
 */
int i8253_ref_suspend(struct device *dev)
{       uint16_t                counter;
        struct i8253_ref        *iref;
 
        counter = 0;
        if (!dev) {
                /* not available device. */
                printk(KERN_ERR "%s: Invalid argument.", __func__);
                return -EINVAL;
        }
        iref = dev_get_drvdata(dev);
        if (!iref) {
                /* not available private context. */
                goto out;
        }
        counter = i8253_ref_counter_read(iref);
 
out:
        dev_info(dev, "Suspended. counter=0x%.4x\n", counter);
        return 0;
}
 
/*
 * Handle event resume.
 * @param dev points struct device.
 * @return int ==0: Success \
 *             <0:  Fail, Negative errno number.
 */
int i8253_ref_resume(struct device *dev)
{       uint16_t                counter;
        struct i8253_ref        *iref;
 
        counter = 0;
        if (!dev) {
                /* not available device. */
                printk(KERN_ERR "%s: Invalid argument.", __func__);
                return -EINVAL;
        }
        iref = dev_get_drvdata(dev);
        if (!iref) {
                /* not available private context. */
                goto out;
        }
        counter = i8253_ref_counter_read(iref);
out:
        dev_info(dev, "Resumed. counter=0x%.4x\n", counter);
        return 0;
}
 
#else /* (defined(CONFIG_PM)) */
/* Power management is not enabled. */
/* Call back pointers are null. */
 
#define i8253_ref_suspend       NULL
#define i8253_ref_resume        NULL
#endif /* (defined(CONFIG_PM)) */
 
/*
 * Probe i8253 PIT refresh counter.
 * @param pdev points platform_device structure.
 * @return int 0:  Success, \
 *             <0: Error, negative errno number.
 */
int i8253_ref_probe(struct platform_device *pdev)
{       struct device                   *dev;
        struct resource                 *res;
        struct i8253_ref                *iref;
        struct i8253_ref_platfrom_data  *pdata;
        uint32_t                        rate_default;
        int                             ret;
 
        ret = 0;
        printk(KERN_INFO "%s: Called. pdev=0x%p, dev=0x%p\n",
                __func__, pdev, &(pdev->dev)
        );
        iref = kzalloc(sizeof(*iref), GFP_KERNEL);
        if (!iref) {
                printk(KERN_ERR "%s: Not enough memory.\n", __func__);
                ret = -ENOMEM;
                goto err;
        }
        res = platform_get_resource_byname(pdev, IORESOURCE_IO, I8253_REF_RESOURCE_REFRESH_COUNTER);
        if (!res) {
                printk(KERN_ERR "%s: Can not found resource. name=%s\n",
                        __func__, I8253_REF_RESOURCE_REFRESH_COUNTER
                );
                ret = -ENODEV;
                goto err;
        }
        /* @note we skip request_resource(),
           drivers/base/platform.c:platform_device_add() claims resource.
        */
        iref->refresh_counter = res->start;
 
        res = platform_get_resource_byname(pdev, IORESOURCE_IO, I8253_REF_RESOURCE_CONTROL_WORD);
        if (!res) {
                printk(KERN_ERR "%s: Can not found resource. name=%s\n",
                        __func__, I8253_REF_RESOURCE_CONTROL_WORD
                );
                ret = -ENODEV;
                goto err;
        }
        /* @note we skip request_resource(),
           drivers/base/platform.c:platform_device_add() claims resource.
        */
        iref->control_word = res->start;
 
        dev = &(pdev->dev);
        pdata = dev->platform_data;
        if (!pdata) {
                printk(KERN_ERR "%s: Missing platform data.\n", __func__);
                ret = -ENODEV;
                goto err;
        }
        iref->channel = pdata->channel;
        rate_default = pdata->rate_default;
        if (rate_default != I8253_REF_RATE_DEFAULT_KEEP) {
                i8253_ref_rate_write(iref, rate_default);
        }
        iref->rate_saved = iref->rate_default = rate_default;
        dev_set_drvdata(dev, iref);
        ret = sysfs_create_group(&(dev->kobj), &i8253_ref_group);
        if (ret != 0) {
                dev_err(dev, "Can not create sysfs nodes.\n");
                dev_set_drvdata(dev, NULL);
                goto err;
        }
        dev_info(dev, "Probed. refresh_counter=0x%lx, control_word=0x%lx, channel=0x%x\n",
                iref->refresh_counter, iref->control_word, iref->channel
        );
        dev_info(dev, "Current state. rate_default=0x%lx, counter=0x%.4x\n",
                (unsigned long)rate_default, i8253_ref_counter_read(iref)
        );
        return ret;
err:
        kfree(iref);
        return ret;
}
 
/*
 * Removed device or detach driver.
 * @param pdev points platform_device structure.
 * @return int 0:  Success, \
 *             <0: Error, negative errno number.
 */
int i8253_ref_remove(struct platform_device *pdev)
{       struct device           *dev;
        struct i8253_ref        *iref;
        int                     ret;
        uint32_t                rate_default;
 
        ret = 0;
        dev = &(pdev->dev);
        dev_info(dev, "Remove. dev=0x%p\n", dev);
        sysfs_remove_group(&(dev->kobj), &i8253_ref_group);
        iref = dev_get_drvdata(dev);
        if (!iref) {
                printk(KERN_ERR "%s: Already removed.\n", __func__);
                ret = -ENODEV;
                goto out;
        }
        rate_default = iref->rate_default;
        if (rate_default != I8253_REF_RATE_DEFAULT_KEEP) {
                i8253_ref_rate_write(iref, rate_default);
        }
        /* Mark removed. */
        dev_set_drvdata(dev, NULL);
        /* Free driver context. */
        kfree(iref);
out:
        return 0;
}
 
/*
 * Shutdown or reboot kernel.
 * @param pdev points platform_device structure.
 */
void i8253_ref_shutdown(struct platform_device *pdev)
{       printk(KERN_INFO "%s: Called. pdev=0x%p, dev=0x%p\n",
                __func__, pdev, &(pdev->dev)
        );
        return;
}
 
/*
 * Suspend and resume power event handler table.
 */
#if (defined(CONFIG_PM))
struct dev_pm_ops i8253_ref_pm = {
        .suspend = i8253_ref_suspend,
        .resume = i8253_ref_resume,
};
#endif /* (defined(CONFIG_PM)) */
 
/*
 * i8253 PIT refresh counter platform driver structure.
 */
struct platform_driver i8253_ref_driver = {
        .driver = {
                .name = I8253_REF_DEVICE_NAME,  /*!< will be matched to device name. */
#if (defined(CONFIG_PM))
                .pm = &i8253_ref_pm,    /*!< power manage methods. */
#endif /* (defined(CONFIG_PM)) */
        },
        .probe = i8253_ref_probe,       /*!< device present or plugged. */
        .remove = i8253_ref_remove,     /*!< device or driver removed. */
        .shutdown = i8253_ref_shutdown, /*!< going halt. */
};
 
/*
 * Tutorial i8253 refresh platform device driver init.
 * @return int == 0: Success. 
 *             < 0:  Fail, negative errno number.
 */
static int __init i8253_ref_init(void)
{       int     ret;
        printk(KERN_INFO "%s: Called.\n", __func__);
        ret = platform_driver_register(&i8253_ref_driver);
        if (ret != 0) {
                /* Can not register driver. */
                printk(KERN_ERR "%s: Can not register driver. ret=%d\n",
                        __func__, ret
                );
        }
        return ret;
}
 
/*
 * Tutorial i8253 refresh platform device driver exit (removing module).
 * @return void
 */
static void __exit i8253_ref_exit(void)
{       printk(KERN_INFO "%s: Called.\n", __func__);
        /* platform_driver_unregister will call _remove driver method. */
        platform_driver_unregister(&i8253_ref_driver);
}
 
module_init(i8253_ref_init);
module_exit(i8253_ref_exit);
 
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Tutorial i8253 refresh platform device driver.");
MODULE_AUTHOR("afuruta@m7.dion.ne.jp");

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-07-27 (木) 09:34:45 (2483d)