字符设备驱动(Linux kernel 4.9.x)
按键驱动
-
数据结构
1.1 设备结构体:缓存键值的buf、缓存按键状态、等待队列、cdev结构体
1.2 定时器:软件延时(防抖)
1.3 按键硬件信息、键值结构体:记录每个按键对应的中断、GPIO、键值
1.4 文件操作结构体:打开、释放、读。 -
流程
2.1 确认按键的流程
(中断)第一次有按键按下中断,屏蔽中断,延时,再次检测看是否仍是按下状态,若是,则确认按下。否则认为是抖动。
2.2 定时器处理流程
上述确认按键流程最后,再次查询按键状态是否仍是按下,若是,则将键值录入缓冲区、唤醒等待队列。同时可以更新并启动新的定时器延迟(可以是一个去抖时间更长的一个时间),此后每次定时器到期后,查询按键是否仍是按下状态,如果是,则重新启用新的延迟。若不是,则开启对应的按键中断,等待新的按键。
input子系统
- 以某触摸屏驱动为例:
struct xxxts {
struct xxx_adc_client *client;
struct device *dev;
struct input_dev *input;
struct clk *clock;
void __iomem *io;
unsigned long xp;
unsigned long yp;
int irq_tc;
int count;
int shift;
int features;
};
static struct xxxts ts;
static int xxx_ts_probe(strcut platform_device *pdev)
{
...
strcut input_dev *input_dev;
...
...
input_dev = input_allocate_device();
...
ts.input = input_dev;
ts.input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
ts.input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
input_set_abs_params(ts.input, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0);
ts.input->name = "XXXXX TouchScreen";
ts.input->id.bustype = BUS_HOST;
ts.input->id.vendor = 0xDEAD;
ts.input->id.product = 0xBEEF;
ts.input->id.version = 0x0102;
ret = request_irq(ts.irq_tc, stylus_irq, 0, "xxx_ts_pen", ts.input);
ret = input_register_device(ts.input);
return 0;
}
- 相关函数
2.1 int input_allocate_device(void); /* 给输入设备分配、初始化内存 */
2.2 int input_register_device(struct input_dev *dev); /* 注册输入设备 */
2.3 void input_report_xxx(struct input_dev \*dev, unsigned int code, int value)); /* 报告发生的事件及相应的键值/坐标等状态 */
input_report_abs(...);
input_report_key(...);
input_report_rel(...);
2.4 void input_sync(strcut input_dev \*dev); /* 事件同步,告知事件接收者驱动已发出了一个完整报告 */
2.5 void input_unregister_device(struct input_dev *dev); /* 注销输入设备 */
NVRAM设备驱动: miscdevice概念
#include <linux/miscdevice.h>
...
...
static const struct file_operations nvram_fops = {
.owner = THIS_MODULE,
.llseek = nvram_llseek,
.read = nvram_read,
.write = nvram_write,
.unlocked_ioctl = nvram_ioctl,
.open = nvram_open,
.release = nvram_release,
};
static struct miscdevice nvram_dev = {
NVRAM_MINOR,
"nvram",
&nvram_fops
};
static int __init nvram_init(void)
{
...
ret = misc_register(&nvram_dev);
...
}
小结: miscdevice共享一个主设备号MISC_MAJOR,但次设备号不同。
所有的miscdevice设备形成一个链表,对设备访问时内核根据次设备号查找对应的miscdevice设备。
看门狗(watchdog)设备驱动:platform_device/platform_driver的概念
static int __init my_wdt_probe(struct platform_device *pdev) /* 省去了返回值判断的代码 */
{
struct my_wdt_device *wdev;
struct watchdog_device *wdog;
struct resource *res;
void __iomem *base;
int ret;
u32 val;
wdev = devm_kzalloc(&pdev->dev, sizeof(*wdev), GFP_KERNEL);
...
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
...
wdev->regmap = devm_regmap_init_mmio_clk(&pdev->dev, NULL, base,
&my_wdt_regmap_config);
...
wdev->clk = devm_clk_get(&pdev->dev, NULL);
...
wdog = &wdev->wdog;
wdog->info = &my_wdt_info;
wdog->ops = &my_wdt_ops;
wdog->min_timeout = 1;
wdog->max_hw_heartbeat_ms = my_WDT_MAX_TIME * 1000;
wdog->parent = &pdev->dev;
ret = platform_get_irq(pdev, 0);
if (ret > 0)
if (!devm_request_irq(&pdev->dev, ret, my_wdt_isr, 0,
dev_name(&pdev->dev), wdog))
wdog->info = &my_wdt_pretimeout_info;
ret = clk_prepare_enable(wdev->clk);
if (ret)
return ret;
regmap_read(wdev->regmap, my_WDT_WRSR, &val);
wdog->bootstatus = val & my_WDT_WRSR_TOUT ? WDIOF_CARDRESET : 0;
wdev->ext_reset = of_property_read_bool(pdev->dev.of_node,
"fsl,ext-reset-output");
wdog->timeout = clamp_t(unsigned, timeout, 1, my_WDT_MAX_TIME);
if (wdog->timeout != timeout)
dev_warn(&pdev->dev, "Initial timeout out of range! Clamped from %u to %u\n",
timeout, wdog->timeout);
platform_set_drvdata(pdev, wdog);
watchdog_set_drvdata(wdog, wdev);
watchdog_set_nowayout(wdog, nowayout);
watchdog_set_restart_priority(wdog, 128);
watchdog_init_timeout(wdog, timeout, &pdev->dev);
if (my_wdt_is_running(wdev)) {
my_wdt_set_timeout(wdog, wdog->timeout);
set_bit(WDOG_HW_RUNNING, &wdog->status);
}
/*
* Disable the watchdog power down counter at boot. Otherwise the power
* down counter will pull down the #WDOG interrupt line for one clock
* cycle.
*/
regmap_write(wdev->regmap, my_WDT_WMCR, 0);
ret = watchdog_register_device(wdog);
...
dev_info(&pdev->dev, "timeout %d sec (nowayout=%d)\n",
wdog->timeout, nowayout);
return 0;
}
/* 采用在dtsi文件里,注册平台设备 */
static const struct of_device_id my_wdt_dt_ids[] = {
{ .compatible = "fsl,my-wdt", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_wdt_dt_ids);
/* 定义平台驱动结构体 */
static struct platform_driver my_wdt_driver = {
.remove = __exit_p(my_wdt_remove),
.shutdown = my_wdt_shutdown,
.driver = {
.name = my-wdt,
.pm = &my_wdt_pm_ops,
.of_match_table = my_wdt_dt_ids,
},
};
module_platform_driver_probe(my_wdt_driver, my_wdt_probe); /* 注册平台驱动及绑定porbe */
附:dtsi文件部分相关内容
wdog1: wdog@30280000 {
compatible = "fsl,my-wdt";
reg = <0 0x30280000 0 0x10000>;
interrupts = <GIC_SPI 78 IRQ_TYPE_LEVEL_HIGH>;
...
status = "disabled";
};
小结: 结合dtsi文件来实现platform driver和platform device的匹配
首先,my_wdt_dt_ids[].compatible与dtsi文件中的compatible的字符串要完全一样;
其次,平台驱动.driver.of_match_table指向my_wdt_dt_ids.