一、前言
讯为官方提供的 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
强迫症解决办法:
- 自己把驱动程序拷贝到本地的源码树中,并自己添加相应的内核配置项,然后在树内编译驱动模块
- 自己在驱动源码种添加一句
MODULE_INFO(intree, "Y");
,以欺骗内核本模块为树内模块