[xhr4412][practice] 设备树 GPIO 总结

1. design

   前面两篇文章,属于在国庆期间胡乱 copy 而成,基本是把内核代码 copy 出来随便改改,还是随便练习一下,写个简单的架构。

   练习一下如何自定义设备树,自定义属性,获取这些属性并使用他们。

   就结合前面两篇,定义一个总设备,和两类子设备。

  1. 第一类子设备:btn + led ,使用 button 控制 led
  2. 第二类子设备:only btn,只有 button,按下后打印信息

1.1 dts

   在 arch/arm/boot/dts/exynos4412-xhr-elite.dts 中添加设备节点。共 5 个子设备,前两个为第一类,其他为第二类。

  • btn-led 该属性表明是第一类,only-btn 表示第二类
  • led-name led 描述,btn-name button 描述
  • led-gpios led 使用的 GPIO,btn-gpios button 使用的 GPIO
	xhr-test-node {
		compatible = "xhr-test-dev";

		dev1 {
			btn-led;
			led-name = "red:system";
			btn-name = "GPIO Key Home";
			led-gpios = <&gpl2 0 GPIO_ACTIVE_HIGH>;
			btn-gpios = <&gpx1 1 GPIO_ACTIVE_LOW>;
		};

		dev2 {
			btn-led;
			led-name = "red:user";
			btn-name = "GPIO Key Back";
			led-gpios = <&gpk1 1 GPIO_ACTIVE_HIGH>;
			btn-gpios = <&gpx1 2 GPIO_ACTIVE_LOW>;
		};

		dev3 {
			only-btn;
			btn-name = "GPIO Key Sleep";
			btn-gpios = <&gpx3 3 GPIO_ACTIVE_LOW>;
		};

		dev4 {
			only-btn;
			btn-name = "GPIO Key Vol+";
			btn-gpios = <&gpx2 1 GPIO_ACTIVE_LOW>;
		};

		dev5 {
			only-btn;
			btn-name = "GPIO Key Vol-";
			btn-gpios = <&gpx2 0 GPIO_ACTIVE_LOW>;
		};
	};

1.2 files

   testdrv.c 主要是支持 linux kernel 的架构与实现两类子设备的抽象,不做具体的设备实现,另外两个 .c 分别对两类设备进行具体实现。

.
├── class					// 子设备
│   ├── btn_led.c			// 第一类子设备
│   └── only_btn.c			// 第二类子设备
├── include
│   ├── class_btn_led.h		// 第一类子设备头文件
│   ├── class_only_btn.h	// 第二类子设备头文件
│   └── xhrdev.h			// 抽象类头文件
├── Makefile
└── testdrv.c				// 抽象类

2 directories, 7 files

1.3 Makefile

   将三个源文件编译进去就可以了,最后编译出 xhrdev.ko

PWD := $(shell pwd)
INCLUDE := -I$(PWD)/include
KDIR := /home/xhr/iTop4412/xhr4412/linux/xhr4412-linux-5.8.5
MODULE := xhrdev
ALLFLAGS := $(INCLUDE)

obj-m := $(MODULE).o

all:
	$(MAKE) -C $(KDIR) EXTRA_CFLAGS=$(ALLFLAGS) M=$(PWD) modules

.PHONY:clean
clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

help:
	$(MAKE) -C $(KDIR) M=$(PWD) help

$(MODULE)-objs += testdrv.o
$(MODULE)-objs += class/only_btn.o \
                  class/btn_led.o

2. implementation

   具体实现就比较简单了,每个模块只干自己的事就好了。

2.1 struct

几个头文件中重要的数据结构:

  1. struct subdev_ops 抽象类的操作函数集,每一类子设备操作集不同
  2. struct cls_btn_led_dev 第一类子设备
  3. struct cls_only_btn_dev 第二类子设备
struct subdev_ops {
	int (*const parse_dts)(struct device* dev,
		struct fwnode_handle *child, void *__res);
	int (*const probe)(struct device* dev, void *__res);
	void (*const remove)(struct device* dev, void *__res);
};

struct cls_btn_led_dev {
	struct gpio_desc *led_gpiod;
	const char *led_desc;
	const char *btn_desc;
	int btn_irq;
	int led_state;
	int count;
};

struct cls_only_btn_dev {
	struct gpio_desc *gpiod;
	const char *desc;
	int gpio;
	int irq;
	int count;
};

2.2 testdrv.c

   主要实现 Linux 设备驱动总线模型的 code,并且对所有设备进行抽象。

#include <linux/module.h>
#include <linux/init.h>
#include <class_btn_led.h>
#include <class_only_btn.h>

#define xprint(fmt, args...) printk("[xhr4412]\033[1;31m[%s][%d] " fmt "\033[0m \n", __func__, __LINE__, ##args)

enum dev_type {
	BTN_LED = 0,
	ONLY_BTN,
	MAX_TYPE,
};

struct subdevdata {
	enum dev_type type;
	const struct subdev_ops *ops;
	union {
		struct cls_only_btn_dev ob;
		struct cls_btn_led_dev bl;
	} dev;
};

struct xhrdev_data {
	int nsubdev;
	struct device *dev;
	struct platform_device *platdev;
	struct subdevdata subdev[];
};

static struct xhrdev_data* xhrdev_get_devtree_data(struct device *dev)
{
	struct fwnode_handle *child;
	struct xhrdev_data *xdev;
	struct subdevdata *subdev;
	int count = device_get_child_node_count(dev);
	int ret;

	xprint("count = %d size=%d", count, struct_size(xdev, subdev, count));
	xprint("size(xhrdev_data)=%d size(subdevdata)=%d",
		sizeof(struct xhrdev_data), sizeof(struct subdevdata));

	xdev = devm_kzalloc(dev, struct_size(xdev, subdev, count), GFP_KERNEL);
	if (!xdev) {
		xprint("no memory !");
		return NULL;
	}

	device_for_each_child_node(dev, child) {
		subdev = &xdev->subdev[xdev->nsubdev++];
		if (fwnode_property_present(child, "btn-led")) {
			subdev->type = BTN_LED;
			subdev->ops  = bl_dev_ops;
		} else if (fwnode_property_present(child, "only-btn")) {
			subdev->type = ONLY_BTN;
			subdev->ops  = ob_dev_ops;
		} else {
			xprint("error: no valid property");
			goto _error;
		}
		ret = subdev->ops->parse_dts(dev, child, &subdev->dev);
		if (ret) {
			xprint("xdev->nsubdev(%d) fail", xdev->nsubdev);
			goto _error;
		}
	}

	return xdev;
_error:
	devm_kfree(dev, xdev);
	return NULL;
}

static int xhrdev_probe(struct platform_device * pdev)
{
	struct device *dev = &pdev->dev;
	struct xhrdev_data *xdev = dev_get_platdata(dev);
	struct subdevdata *subdev;
	int i, ret;

	if (xdev) {
		xprint("platdata is not NULL xdev = %p", xdev);
		return -EINVAL;
	}

	xdev = xhrdev_get_devtree_data(dev);
	if (!xdev) {
		xprint("can't get valid devtree data");
		return -EINVAL;
	}

	xdev->dev = dev;
	xdev->platdev = pdev;

	for (i = 0; i < xdev->nsubdev; i++) {
		subdev = &xdev->subdev[i];
		ret = subdev->ops->probe(dev, &subdev->dev);
		if (ret) {
			xprint("xdev->subdev[%d] probe fail: ret = %d", i, ret);
			goto _error_probe;
		}
	}

	platform_set_drvdata(pdev, xdev);
	return 0;

_error_probe:
	for (i--; i >= 0; i--) {
		subdev = &xdev->subdev[i];
		subdev->ops->remove(dev, subdev);
	}
	return ret;
}

static int xhrdev_remove(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct xhrdev_data *xdev;
	int i;

	xdev = platform_get_drvdata(pdev);

	for (i = 0; i < xdev->nsubdev; i++) {
		xdev->subdev[i].ops->remove(dev, &xdev->subdev[i]);
	}

	return 0;
}

static const struct of_device_id xhrdev_of_match[] = {
	{ .compatible = "xhr-test-dev", },
	{ },
};
MODULE_DEVICE_TABLE(of, xhrdev_of_match);

static struct platform_driver xhrdev_platform_driver = {
	.probe		= xhrdev_probe,
	.remove		= xhrdev_remove,
	.driver		= {
		.name	= "xhr-test-node", // match dts node name
		.of_match_table = xhrdev_of_match,
	}
};

static int __init xhrdev_init(void)
{
	return platform_driver_register(&xhrdev_platform_driver);
}

static void __exit xhrdev_exit(void)
{
	platform_driver_unregister(&xhrdev_platform_driver);
}

late_initcall(xhrdev_init);
module_exit(xhrdev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("xhr <xhr@xhr.com>");
MODULE_DESCRIPTION("xhr4412 driver for xhr-test-dev");
MODULE_ALIAS("xhr platform:xhr-test-node");

2.3 btn_led.c

#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>

#include <class_btn_led.h>

#define xprint(fmt, args...) printk("[xhr4412]\033[1;31m[%s][%d] " fmt "\033[0m \n", __func__, __LINE__, ##args)

static irqreturn_t bl_dev_irq_handler(int irqnum, void *data)
{
	struct cls_btn_led_dev *bldev = data;
	int ret;

	bldev->count++;
	bldev->led_state = !bldev->led_state;

	ret = gpiod_direction_output(bldev->led_gpiod, bldev->led_state);
	if (unlikely(ret)) {
		xprint("gpiod_direction_output fail, %s(%d) ret = %d\n",
			bldev->led_desc, bldev->led_state, ret);
	}
	return IRQ_HANDLED;
}

static int bl_dev_probe(struct device* dev, void *__res)
{
	struct cls_btn_led_dev *bldev = __res;
	int ret;

	ret = gpiod_direction_output(bldev->led_gpiod, bldev->led_state);
	if (ret) {
		xprint("gpiod_direction_output fail, led_state(%d) ret = %d\n",
			bldev->led_state, ret);
		return ret;
	}

	return devm_request_irq(dev, bldev->btn_irq, bl_dev_irq_handler,
		IRQF_TRIGGER_FALLING, bldev->btn_desc, bldev);
}

static void bl_dev_remove(struct device* dev, void *__res)
{
}

static int bl_dev_parse_dts(struct device* dev, struct fwnode_handle *child, void *__res)
{
	struct cls_btn_led_dev *bldev = __res;
	struct gpio_desc *btn_gpiod;

	if (!bldev || !dev || !child) {
		xprint("input paras mustn't be NULL dev=%p child=%p bldev=%p",
			dev, child, bldev);
		return -EINVAL;
	}

	if (!fwnode_property_present(child, "btn-led")) {
		xprint("no <btn-led> property");
		return -EINVAL;
	}

	bldev->led_gpiod = devm_fwnode_get_gpiod_from_child(dev, "led", child, GPIOD_ASIS, NULL);
	if (IS_ERR_OR_NULL(bldev->led_gpiod)) {
		xprint("can't get bldev->led_gpiod");
		return -EINVAL;
	}

	btn_gpiod = devm_fwnode_get_gpiod_from_child(dev, "btn", child, GPIOD_ASIS, NULL);
	if (IS_ERR_OR_NULL(btn_gpiod)) {
		xprint("can't get btn_gpiod");
		return -EINVAL;
	}

	bldev->btn_irq = gpio_to_irq(desc_to_gpio(btn_gpiod));

	fwnode_property_read_string(child, "btn-name", &bldev->btn_desc);
	fwnode_property_read_string(child, "led-name", &bldev->led_desc);

	xprint("BL: led_desc=%s btn_irq=%d", bldev->led_desc, bldev->btn_irq);
	return 0;
}

static const struct subdev_ops _bl_dev_ops = {
	.parse_dts	= bl_dev_parse_dts,
	.probe		= bl_dev_probe,
	.remove		= bl_dev_remove,
};

const struct subdev_ops *const bl_dev_ops = &_bl_dev_ops;

2.4 only_btn.c

#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>

#include <class_only_btn.h>

#define xprint(fmt, args...) printk("[xhr4412]\033[1;31m[%s][%d] " fmt "\033[0m \n", __func__, __LINE__, ##args)

static irqreturn_t ob_dev_irq_handler(int irqnum, void *data)
{
	struct cls_only_btn_dev *obdev = data;
	
	xprint("btn-desc=%s count=%d", obdev->desc, obdev->count++);
	return IRQ_HANDLED;
}

static int ob_dev_probe(struct device* dev, void *__res)
{
	struct cls_only_btn_dev *obdev = __res;
	return devm_request_irq(dev, obdev->irq, ob_dev_irq_handler,
		IRQF_TRIGGER_FALLING, obdev->desc, obdev);
}

static void ob_dev_remove(struct device* dev, void *__res)
{
}

static int ob_dev_parse_dts(struct device* dev, struct fwnode_handle *child, void *__res)
{
	struct cls_only_btn_dev *obdev = __res;

	if (!obdev || !dev || !child) {
		xprint("input paras mustn't be NULL dev=%p child=%p obdev=%p",
			dev, child, obdev);
		return -EINVAL;
	}

	if (!fwnode_property_present(child, "only-btn")) {
		xprint("no <only-btn> property");
		return -EINVAL;
	}

	obdev->gpiod = devm_fwnode_get_gpiod_from_child(dev, "btn", child, GPIOD_ASIS, NULL);

	if (IS_ERR_OR_NULL(obdev->gpiod)) {
		xprint("can't get gpiod");
		return -EINVAL;
	}

	obdev->gpio = desc_to_gpio(obdev->gpiod);
	obdev->irq  = gpio_to_irq(obdev->gpio);

	fwnode_property_read_string(child, "btn-name", &obdev->desc);

	xprint("OB: gpiod=%p gpio=%d irq=%d desc=%s",
		obdev->gpiod, obdev->gpio, obdev->irq, obdev->desc);

	return 0;
}

static const struct subdev_ops _ob_dev_ops = {
	.parse_dts	= ob_dev_parse_dts,
	.probe		= ob_dev_probe,
	.remove		= ob_dev_remove,
};

const struct subdev_ops *const ob_dev_ops = &_ob_dev_ops;

3. test

3.1 insmod & probe

在这里插入图片描述

3.2 button

   btn_led 的设备只改变 led,没有 log。

在这里插入图片描述

4. issue

4.1 unexpected IRQ

   unexpected IRQ trap at vector 00

   红字 log 疯狂刷屏,其实不是 bug,这是由于我的 uboot 把 home 按键使能了中断。uboot 跳转到 linux 后并不会初始化关闭所有中断,所以当按下这个按键时,就会出现 “unexpected IRQ”,因为 linux 并没有分配该中断的中断号,linux 认为并不应该发生这个中断。

   解决办法是将 uboot 中的 enable_home_btn_irq() 函数去掉,或是跳转到 linux 前关闭这个中断,或者在注册这个中断前不要手欠按这个 button,注册成功后就没有问题了。

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
XHR 是 XMLHttpRequest 的缩写,它是一种可以在不重新加载整个页面的情况下,通过 JavaScript 发送 HTTP 请求和接收服务器响应的技术。在网页开发中,我们经常会用到 XHR 来实现异步数据交互,比如获取远程数据、发送表单数据等等。 要实现一个简单的 XHR 请求,我们可以通过以下几个步骤来操作: 1. 创建一个 XMLHttpRequest 对象 2. 指定请求的方法、URL 和是否异步 3. 发送请求 4. 监听 XHR 对象的状态变化,并在接收到响应后处理数据 下面是一个简单的示例代码: ```javascript var xhr = new XMLHttpRequest(); xhr.open('GET', 'https://api.example.com/data', true); xhr.send(); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { console.log('成功收到响应:', xhr.responseText); } else { console.log('请求出错:', xhr.status); } } }; ``` 在这个例子中,我们创建了一个 XHR 对象,并使用 GET 方法向 https://api.example.com/data 发送了一个异步请求。然后,我们监听 XHR 对象的状态变化,当 readyState 变为 XMLHttpRequest.DONE 时,表示请求完成。此时我们可以通过 status 属性来判断请求的状态,通过 responseText 属性来获取服务器返回的数据。 总之,XHR 是一种非常有用的技术,可以让我们在网页中实现更加流畅的用户体验,并为用户提供更加丰富的交互功能。希望这个回答能够帮助你更好地了解并使用 XHR

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值