目录
- 一、使用设备树给DM9000网卡_触摸屏指定中断
- 1、修改方法
- 2、实验方法
- 二、在设备树中时钟的简单使用
- 1、参考文档
- 2、知识讲解
- 三、在设备树中pinctrl的简单使用
- 1、几个概念
- 2、设备树中pinctrl节点
- 3、platform_device, platform_driver匹配
- 4、驱动中想选择、设置某个状态的引脚
- 四、使用设备树给LCD指定各种参数
- 1、实验方法
- 2、细节分析
一、使用设备树给DM9000网卡_触摸屏指定中断
1、修改方法
(1)根据设备节点的compatible属性,
(2)在驱动程序中构造/注册 platform_driver,
(3)在 platform_driver 的 probe 函数中获得中断资源
2、实验方法
(1)以下是修改好的代码与之前的变化:
dm9dev9000c.c
下面的是改动后新增部分(原本是没有的),放在代码文件最后即可:
//下面部分的代码,简单来说就是原有的网络设备驱动的基础上又增加了一层封装
//使用总线设备驱动模型,从而使用设备树描述的硬件资源
static int dm9000drv_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *dp_node = dev->of_node;
struct resource *res;
int i;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res) {
irq = res->start;
printk("get dm9000 irq %d\n", irq);
}
else {
printk("can not get irq res for dm9000\n");
return -1;
}
return dm9000c_init();
}
static int dm9000drv_remove(struct platform_device *pdev)
{
dm9000c_exit();
return 0;
}
static const struct of_device_id of_match_dm9000[] = {
{ .compatible = "davicom,dm9000", .data = NULL },
{ /* sentinel */ }
};
struct platform_driver dm9000_drv = {
.probe = dm9000drv_probe,
.remove = dm9000drv_remove,
.driver = {
.name = "dm9000",
.of_match_table = of_match_dm9000, /* 能支持哪些来自于dts的platform_device */
}
};
static int dm9000drv_init(void)
{
platform_driver_register(&dm9000_drv);
return 0;
}
static void dm9000drv_exit(void)
{
platform_driver_unregister(&dm9000_drv);
}
module_init(dm9000drv_init);
module_exit(dm9000drv_exit);
MODULE_LICENSE("GPL");
s3c_ts.c
下面的是改动后新增部分(原本是没有的),放在代码文件最后即可:
static int s3cts_drv_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *dp_node = dev->of_node;
struct resource *res;
int i;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res) {
irq_tc = res->start;
printk("get touchscreen's tc irq %d\n", irq_tc);
}
else {
printk("can not get irq res for touchscreen's tc\n");
return -1;
}
res = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
if (res) {
irq_adc = res->start;
printk("get touchscreen's adc irq %d\n", irq_adc);
}
else {
printk("can not get irq res for touchscreen's adc\n");
return -1;
}
return s3c_ts_init();
}
static int s3cts_drv_remove(struct platform_device *pdev)
{
s3c_ts_exit();
return 0;
}
static const struct of_device_id of_match_s3cts[] = {
{ .compatible = "jz2440,ts", .data = NULL },
{ /* sentinel */ }
};
struct platform_driver s3cts_drv = {
.probe = s3cts_drv_probe,
.remove = s3cts_drv_remove,
.driver = {
.name = "s3c_ts",
.of_match_table = of_match_s3cts, /* 能支持哪些来自于dts的platform_device */
}
};
static int s3cts_drv_init(void)
{
platform_driver_register(&s3cts_drv);
return 0;
}
static void s3cts_drv_exit(void)
{
platform_driver_unregister(&s3cts_drv);
}
module_init(s3cts_drv_init);
module_exit(s3cts_drv_exit);
MODULE_LICENSE("GPL");
(2)将修改后的文件放置到内核如下目录:
drivers/net/ethernet/davicom
drivers/input/touchscreen
a. 编译内核
b. 使用新的uImage启动
c. 测试网卡:
ifconfig eth0 192.168.1.101
ping 192.168.1.1
d. 测试触摸屏:
hexdump /dev/evetn0 // 然后点击触摸屏
hexdump命令看任何文件的十六进制编码,一般看“二进制”文件。
(3)本部分完整代码链接如下:
第6课第1节_网卡_触摸屏驱动
https://www.aliyundrive.com/s/MgCwdpYGwFL
点击链接保存,或者复制本段内容,打开「阿里云盘」APP ,无需下载极速在线查看,
视频原画倍速播放。
二、在设备树中时钟的简单使用
1、参考文档
内核 Documentation/devicetree/bindings/clock/clock-bindings.txt
内核 Documentation/devicetree/bindings/clock/samsung,s3c2410-clock.txt
2、知识讲解
(1)设备树中定义了各种时钟, 在文档中称之为"Clock providers", 比如:
clocks: clock-controller@4c000000 {
compatible = "samsung,s3c2440-clock";
reg = <0x4c000000 0x20>;
#clock-cells = <1>; // 想使用这个clocks时要提供1个u32来指定它, 比如选择这个clocks中发出的LCD时钟、PWM时钟
};
(2) 设备需要时钟时, 它是"Clock consumers", 它描述了使用哪一个"Clock providers"中的哪一个时钟(id), 比如:
fb0: fb@4d000000{
compatible = "jz2440,lcd";
reg = <0x4D000000 0x60>;
interrupts = <0 0 16 3>;
clocks = <&clocks HCLK_LCD>; // 使用clocks即clock-controller@4c000000中的HCLK_LCD
};
(3)驱动中获得/使能时钟:
// 确定时钟个数
int nr_pclks = of_count_phandle_with_args(dev->of_node, "clocks",
"#clock-cells");
// 获得时钟
for (i = 0; i < nr_pclks; i++) {
struct clk *clk = of_clk_get(dev->of_node, i);
}
// 使能时钟
clk_prepare_enable(clk);
// 禁止时钟
clk_disable_unprepare(clk);
三、在设备树中pinctrl的简单使用
文档:
内核 Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt
1、几个概念
Bank: 以引脚名为依据, 这些引脚分为若干组, 每组称为一个Bank
比如s3c2440里有GPA、GPB、GPC等Bank,
每个Bank中有若干个引脚, 比如GPA0,GPA1, ..., GPC0, GPC1,...等引脚
Group: 以功能为依据, 具有相同功能的引脚称为一个Group
比如s3c2440中串口0的TxD、RxD引脚使用 GPH2,GPH3, 那这2个引脚可以列为一组
比如s3c2440中串口0的流量控制引脚使用 GPH0,GPH1, 那这2个引脚也可以列为一组
State: 设备的某种状态, 比如内核自己定义的"default","init","idel","sleep"状态;
也可以是其他自己定义的状态, 比如串口的"flow_ctrl"状态(使用流量控制)
设备处于某种状态时, 它可以使用若干个Group引脚
2、设备树中pinctrl节点
a.1 它定义了各种 pin bank, 比如s3c2440有GPA,GPB,GPC,...,GPB各种BANK, 每个BANK
中有若干引脚:
pinctrl_0: pinctrl@56000000 {
reg = <0x56000000 0x1000>;
gpa: gpa {
gpio-controller;
#gpio-cells = <2>; /* 以后想使用gpa bank中的引脚时, 需要2个u32来指定引脚 */
};
gpb: gpb {
gpio-controller;
#gpio-cells = <2>;
};
gpc: gpc {
gpio-controller;
#gpio-cells = <2>;
};
gpd: gpd {
gpio-controller;
#gpio-cells = <2>;
};
};
a.2 它还定义了各种group(组合), 某种功能所涉及的引脚称为group,
比如串口0要用到2个引脚: gph0, gph1:
uart0_data: uart0-data {
samsung,pins = "gph-0", "gph-1";
samsung,pin-function = <2>; /* 在GPHCON寄存器中gph0,gph1可以设置以下值:
0 --- 输入功能
1 --- 输出功能
2 --- 串口功能
我们要使用串口功能,
samsung,pin-function 设置为2
*/
};
uart0_sleep: uart0_sleep {
samsung,pins = "gph-0", "gph-1";
samsung,pin-function = <0>; /* 在GPHCON寄存器中gph0,gph1可以设置以下值:
0 --- 输入功能
1 --- 输出功能
2 --- 串口功能
我们要使用输入功能,
samsung,pin-function 设置为0
*/
};
a.3 设备节点中要使用某一个 pin group
serial@50000000 {
......
pinctrl-names = "default", "sleep"; /* 既是名字, 也称为state(状态) */
pinctrl-0 = <&uart0_data>;
pinctrl-1 = <&uart0_sleep>;
};
pinctrl-names中定义了2种state: default 和 sleep,
default 对应的引脚是: pinctrl-0, 它指定了使用哪些pin group: uart0_data
sleep 对应的引脚是: pinctrl-1, 它指定了使用哪些pin group: uart0_sleep
3、platform_device, platform_driver匹配
之前的文章中讲解过platform_device和platform_driver的匹配过程,
最终都会调用到 really_probe (drivers/base/dd.c)
really_probe:
/* If using pinctrl, bind pins now before probing */
ret = pinctrl_bind_pins(dev);
dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_DEFAULT); /* 获得"default"状态的pinctrl */
dev->pins->init_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_INIT); /* 获得"init"状态的pinctrl */
ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state); /* 优先设置"init"状态的引脚 */
ret = pinctrl_select_state(dev->pins->p, dev->pins->default_state); /* 如果没有init状态, 则设置"default"状态的引脚 */
......
ret = drv->probe(dev);
所以: 如果设备节点中指定了pinctrl, 在对应的probe函数被调用之前, 先"bind pins", 即先绑定、设置引脚
4、驱动中想选择、设置某个状态的引脚
devm_pinctrl_get_select_default(struct device *dev); // 使用"default"状态的引脚
pinctrl_get_select(struct device *dev, const char *name); // 根据name选择某种状态的引脚
pinctrl_put(struct pinctrl *p); // 不再使用, 退出时调用
四、使用设备树给LCD指定各种参数
参考文章:
让TQ2440也用上设备树(1):
http://www.cnblogs.com/pengdonglin137/p/6241895.html
参考代码: https://github.com/pengdonglin137/linux-4.9/blob/tq2440_dt/drivers/video/fbdev/s3c2410fb.c
1、实验方法
所使用全部源码:
第6课第4节_LCD驱动
https://www.aliyundrive.com/s/AkFjPKLqWmJ
点击链接保存,或者复制本段内容,打开「阿里云盘」APP ,无需下载极速在线查看,
视频原画倍速播放。
a. 替换dts文件:
把"jz2440_irq.dts" 放入内核 arch/arm/boot/dts目录,
b. 替换驱动文件:
把"s3c2410fb.c" 放入内核 drivers/video/fbdev/ 目录,
修改 内核 drivers/video/fbdev/Makefile :
obj-$(CONFIG_FB_S3C2410) += lcd_4.3.o
改为:
obj-$(CONFIG_FB_S3C2410) += s3c2410fb.o
c. 编译驱动、编译dtbs:
export PATH=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/work/system/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi/bin
cp config_ok .config
make uImage // 生成 arch/arm/boot/uImage
make dtbs // 生成 arch/arm/boot/dts/jz2440_irq.dtb
d. 使用上述uImage, dtb启动内核即可看到LCD有企鹅出现
2、细节分析
设备树中的描述:
fb0: fb@4d000000{
compatible = "jz2440,lcd";
reg = <0x4D000000 0x60>;
interrupts = <0 0 16 3>;
clocks = <&clocks HCLK_LCD>; /* a. 时钟 */
clock-names = "lcd";
pinctrl-names = "default"; /* b. pinctrl */
pinctrl-0 = <&lcd_pinctrl &lcd_backlight &gpb0_backlight>;
status = "okay";
/* c. 根据LCD引脚特性设置lcdcon5, 指定lcd时序参数 */
lcdcon5 = <0xb09>;
type = <0x60>;
width = /bits/ 16 <480>;
height = /bits/ 16 <272>;
pixclock = <100000>; /* 单位: ps, 10^-12 S, */
xres = /bits/ 16 <480>;
yres = /bits/ 16 <272>;
bpp = /bits/ 16 <16>;
left_margin = /bits/ 16 <2>;
right_margin =/bits/ 16 <2>;
hsync_len = /bits/ 16 <41>;
upper_margin = /bits/ 16 <2>;
lower_margin = /bits/ 16 <2>;
vsync_len = /bits/ 16 <10>;
};
&pinctrl_0 {
gpb0_backlight: gpb0_backlight {
samsung,pins = "gpb-0";
samsung,pin-function = <1>;
samsung,pin-val = <1>;
};
};
代码中的处理:
a. 时钟:
info->clk = of_clk_get(dev->of_node, 0);
clk_prepare_enable(info->clk);
b. pinctrl:
代码中无需处理, 在 platform_device/platform_driver匹配之后就会设置"default"
状态对应的pinctrl
c. 根据LCD引脚特性设置lcdcon5, 指定lcd时序参数:
直接读设备树节点中的各种属性值, 用来设置驱动参数
of_property_read_u32(np, "lcdcon5", (u32 *)(&display->lcdcon5));
of_property_read_u32(np, "type", &display->type);
of_property_read_u16(np, "width", &display->width);
of_property_read_u16(np, "height", &display->height);
of_property_read_u32(np, "pixclock", &display->pixclock);
of_property_read_u16(np, "xres", &display->xres);
of_property_read_u16(np, "yres", &display->yres);
of_property_read_u16(np, "bpp", &display->bpp);
of_property_read_u16(np, "left_margin", &display->left_margin);
of_property_read_u16(np, "right_margin", &display->right_margin);
of_property_read_u16(np, "hsync_len", &display->hsync_len);
of_property_read_u16(np, "upper_margin", &display->upper_margin);
of_property_read_u16(np, "lower_margin", &display->lower_margin);
of_property_read_u16(np, "vsync_len", &display->vsync_len);
注:本文章参考了《韦东山老师嵌入式课程》笔记,并结合了自己的实际开发经历以及网上他人的技术文章,综合整理得到。如有侵权,联系删除!水平有限,欢迎各位在评论区交流