[xhr4412][practice] 设备树 GPIO 操作

本文档详细记录了在Linux kernel 5.8.5中仿照leds-gpio.c开发GPIO LED驱动的过程,包括Makefile配置、代码实现及设备操作。作者通过创建和注册misc设备来控制GPIO,实现LED的开关功能。此外,还探讨了加载外部模块对kernel的潜在影响及解决方案。
摘要由CSDN通过智能技术生成

一、前言

   讯为官方提供的 wifi 模块,在买来板子时在安卓系统中有正常使用过,说明 wifi 模块没有问题,但是由于官方提供的 wifi 模块只支持 linux-3.0 kernel,无法在新移植的 linux-5.8.5 中使用,很是蛋疼。

   虽然可以使用网线,但是终究很不方便,一定要做到开发板只需要电源线才可以。

   想直接开始移植联x科的 wifi 模块驱动有些难度,因为对设备树其实还没有那么熟悉,所以还是要从最基础的开始入手熟悉一下,再开始移植。

   这里主要仿造 drivers/leds/leds-gpio.c 来学习设备树与 GPIO 的操作方法。

二、Makefile

INCLUDES = -I. -I$(KDIR)/include
MODULE := xhrled
obj-m := $(MODULE).o
KDIR := /home/xhr/iTop4412/xhr4412/linux/xhr4412-linux-5.8.5
PWD := $(shell pwd)

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

.PHONY:clean
clean:
	rm -f *.o *.ko *.symvers *.order *.mod.c *.mod .*.cmd

$(MODULE)-objs += led_drv.o

三、code

   linux kernel 有完整的 struct led_classdev 子系统,不过这里主要是为了简单的学习 GPIO 的操作,所以使用普通的 struct miscdevice 来测试学习。

点灯:

  • echo 1 > /dev/led0
  • echo 1 > /dev/led1

灭灯:

  • echo 0 > /dev/led0
  • echo 0 > /dev/led1
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>

#define xprint(fmt, args...) \
	printk("[xhr4412][%s][%d] " fmt, __func__, __LINE__, ##args)

struct gpio_led_data {
	char name[8];
	struct miscdevice miscdev;
	struct gpio_desc *gpiod;
	u8 can_sleep;
	u8 blinking;
	int state;
	gpio_blink_set_t platform_gpio_blink_set;
};

struct gpio_leds_priv {
	int num_leds;
	struct gpio_led_data leds[];
};

static int gpio_leds_fops_open(struct inode *pnode, struct file *pfile)
{ return 0; }
static int gpio_leds_fops_release(struct inode *pnode, struct file *pfile)
{ return 0; }
static ssize_t gpio_leds_fops_read(struct file *pfile, char __user *userbuf, size_t len, loff_t *loff)
{
	struct miscdevice *misc = pfile->private_data;
	struct gpio_led_data *led = container_of(misc, struct gpio_led_data, miscdev);
	xprint("%s state = %d\n", led->name, led->state);
	return 0;
}
static ssize_t gpio_leds_fops_write(struct file *pfile, const char __user *userbuf, size_t len, loff_t *loff)
{
	struct gpio_led_data *led = container_of(pfile->private_data, struct gpio_led_data, miscdev);
	char buf[1];
	int ret;
	if(!copy_from_user(buf, userbuf, 1)) {
		if (buf[0] == '0')
			led->state = 0;
		else led->state = 1;
		if ((ret = gpiod_direction_output(led->gpiod, led->state)))
			xprint("gpiod_direction_output fault code = %d\n", ret);
	}
	return len;
}

static const struct file_operations gpio_leds_fops = {
	.owner	= THIS_MODULE,
	.open	= gpio_leds_fops_open,
	.release= gpio_leds_fops_release,
	.read	= gpio_leds_fops_read,
	.write	= gpio_leds_fops_write,
};

static int create_gpio_led(const struct gpio_led *template,
	struct gpio_led_data *led_dat, struct device *parent,
	struct fwnode_handle *fwnode, const char *ledname)
{
	int ret;

	led_dat->miscdev.fops  = &gpio_leds_fops;
	led_dat->miscdev.name  = ledname;
	led_dat->miscdev.minor = MISC_DYNAMIC_MINOR;

	if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) {
		led_dat->state = gpiod_get_value_cansleep(led_dat->gpiod);
		if (led_dat->state < 0)
			return led_dat->state;
	} else {
		led_dat->state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
	}

	led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod);

	ret = misc_register(&led_dat->miscdev);
	xprint("misc_register ret = %d msic=%x\n", ret, (int)&led_dat->miscdev);
	if (ret)
		return ret;

	ret = gpiod_direction_output(led_dat->gpiod, led_dat->state);
	if (ret < 0)
		return ret;
	
	return ret;
}

static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct fwnode_handle *child;
	struct gpio_leds_priv *priv;
	int count;

	count = device_get_child_node_count(dev);
	xprint("leds child node count = %d\n", count);
	if (!count)
		return ERR_PTR(-ENODEV);

	priv = devm_kzalloc(dev, sizeof(struct gpio_leds_priv) +
		(sizeof(struct gpio_led_data) * count), GFP_KERNEL);
	if (!priv)
		return ERR_PTR(-ENOMEM);

	device_for_each_child_node(dev, child) {
		struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];
		struct gpio_led led = {};
		const char *state = NULL;
		int ret;

		sprintf(led_dat->name, "led%d", priv->num_leds);
		/*
		 * Acquire gpiod from DT with uninitialized label, which
		 * will be updated after LED class device is registered,
		 * Only then the final LED name is known.
		 */
		led.gpiod = devm_fwnode_get_gpiod_from_child(
			dev, NULL, child, GPIOD_ASIS, NULL);
		if (IS_ERR(led.gpiod)) {
			fwnode_handle_put(child);
			return ERR_CAST(led.gpiod);
		}

		led_dat->gpiod = led.gpiod;

		fwnode_property_read_string(child, "linux,default-trigger", &led.default_trigger);

		if (!fwnode_property_read_string(child, "default-state", &state)) {
			if (!strcmp(state, "keep"))
				led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
			else if (!strcmp(state, "on"))
				led.default_state = LEDS_GPIO_DEFSTATE_ON;
			else
				led.default_state = LEDS_GPIO_DEFSTATE_OFF;
		}

		if (fwnode_property_present(child, "retain-state-suspended"))
			led.retain_state_suspended = 1;
		if (fwnode_property_present(child, "retain-state-shutdown"))
			led.retain_state_shutdown = 1;
		if (fwnode_property_present(child, "panic-indicator"))
			led.panic_indicator = 1;

		ret = create_gpio_led(&led, led_dat, dev, child, led_dat->name);
		if (ret < 0) {
			fwnode_handle_put(child);
			return ERR_PTR(ret);
		}
		/* Set gpiod label to match the corresponding LED name. */
		gpiod_set_consumer_name(led_dat->gpiod, led_dat->name);
		priv->num_leds++;
	}

	return priv;
}

static int gpio_led_probe(struct platform_device *pdev)
{
	struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
	struct gpio_leds_priv *priv;
	if (pdata) {
		xprint("led num = %d\n", pdata->num_leds);
		xprint("not support !\n");
		return -EINVAL;
	}
	else {
		priv = gpio_leds_create(pdev);
		if (IS_ERR(priv))
			return PTR_ERR(priv);
	}
	platform_set_drvdata(pdev, priv);
	return 0;
}

static int gpio_led_remove(struct platform_device *pdev)
{
	struct gpio_leds_priv *priv = platform_get_drvdata(pdev);
	int i;
	for (i = 0;i < priv->num_leds; i++) {
		misc_deregister(&priv->leds[i].miscdev);
		xprint("misc_deregister i = %d\n", i);
	}
	return 0;
}

static const struct of_device_id of_gpio_leds_match[] = {
	{ .compatible = "xhr-gpio-leds", },
	{},
};

MODULE_DEVICE_TABLE(of, of_gpio_leds_match);

static struct platform_driver gpio_led_driver = {
	.probe		= gpio_led_probe,
	.remove		= gpio_led_remove,
	.driver		= {
		.name	= "xhr-gpio-leds",
		.of_match_table = of_gpio_leds_match,
	},
};

module_platform_driver(gpio_led_driver);

MODULE_AUTHOR("xhr <xhr4412@xhr.com>");
MODULE_DESCRIPTION("GPIO LED driver");
MODULE_LICENSE("GPL");

四、note

1. loading out-of-tree module taints kernel

强迫症解决办法:

  1. 自己把驱动程序拷贝到本地的源码树中,并自己添加相应的内核配置项,然后在树内编译驱动模块
  2. 自己在驱动源码种添加一句 MODULE_INFO(intree, "Y");,以欺骗内核本模块为树内模块

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值