【Linux驱动】platform 设备驱动分离(二)—— LED驱动(无设备树)

在没有设备树的情况下,需要手动注册 platform 设备,下面分别注册platform 设备 和 platform 驱动,以此来驱动LED。

一、platform 设备注册

新建文件 led-device.c 文件,该文件保存的是外设信息,在当前背景下便是 LED 相关的寄存器信息,该文件最终会被编程成 ko 模块文件,加入到 Linux 内核中。

1、注册 / 注销 platform 设备

在 Linux 内核中使用 platform_device 类型来表示 platform 设备,platform_device 类型的实例化对象为 leddevice,重点是 name、num_resources、resource 成员的初始化:

  • name:platform设备名,用于和下面 platform 驱动进行匹配
  • release:dev 成员下的 release 函数,当platform 设备从内核移除时调用
  • num_resources:resource 数组的资源个数(数组中元素个数)
  • resource:资源数组,每一个元素都对应一个寄存器的相关信息
static struct platform_device leddevice = {
    .name = "imx6ull-led",
    .id = -1,
    // .dev = {
    //     .release = led_release,
    // },
    .num_resources = ARRAY_SIZE(led_resources),
    .resource = led_resources,
};

static int __init leddevice_init(void)
{
    return platform_device_register(&leddevice);
}

static void __exit leddevice_exit(void)
{
    platform_device_unregister(&leddevice);
}

module_init(leddevice_init);
module_exit(leddevice_exit);

2、resource 数组定义

现定义一个 resource 类型的数组,数组名为 led_resources,数组中的每一个元素都对应一个寄存器。resource 类型数组成员的解析放在上一篇。

#define CCM_CCGR1_BASE							0x20C406C			// 时钟源
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03_BASE	0x20E0068			// IO 复用
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03_BASE	0x20E02F4			// 复用引脚初始化
#define GPIO1_GDIR_BASE							0x209C004			// 输出方向
#define GPIO1_DR_BASE							0x209C000			// LED输出引脚
#define REGISTER_LENGTH                         0x04                // 寄存器地址长度

static struct resource led_resources[] = {
    [0] = {
        .start = CCM_CCGR1_BASE,
        .end = CCM_CCGR1_BASE + REGISTER_LENGTH - 1,
        .flags = IORESOURCE_MEM
    },
    [1] = {
        .start = IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03_BASE,
        .end = IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1,
        .flags = IORESOURCE_MEM
    },
    [2] = {
        .start = IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03_BASE,
        .end = IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1,
        .flags = IORESOURCE_MEM
    },
    [3] = {
        .start = GPIO1_GDIR_BASE,
        .end = GPIO1_GDIR_BASE + REGISTER_LENGTH - 1,
        .flags = IORESOURCE_MEM
    },
    [4] = {
        .start = GPIO1_DR_BASE,
        .end = GPIO1_DR_BASE + REGISTER_LENGTH - 1,
        .flags = IORESOURCE_MEM
    },
};

3、完整 platform 设备注册

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>			// cdev_init
#include <linux/device.h>		// device_create
#include <linux/err.h>			// IS_ERR
#include <asm/io.h>				// ioremap、iounmap
#include <linux/platform_device.h>

/* 寄存器物理地址 */
#define CCM_CCGR1_BASE							0x20C406C			// 时钟源
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03_BASE	0x20E0068			// IO 复用
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03_BASE	0x20E02F4			// 复用引脚初始化
#define GPIO1_GDIR_BASE							0x209C004			// 输出方向
#define GPIO1_DR_BASE							0x209C000			// LED输出引脚
#define REGISTER_LENGTH                         0x04                // 寄存器地址长度

static struct resource led_resources[] = {
    [0] = {
        .start = CCM_CCGR1_BASE,
        .end = CCM_CCGR1_BASE + REGISTER_LENGTH - 1,
        .flags = IORESOURCE_MEM
    },
    [1] = {
        .start = IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03_BASE,
        .end = IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1,
        .flags = IORESOURCE_MEM
    },
    [2] = {
        .start = IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03_BASE,
        .end = IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1,
        .flags = IORESOURCE_MEM
    },
    [3] = {
        .start = GPIO1_GDIR_BASE,
        .end = GPIO1_GDIR_BASE + REGISTER_LENGTH - 1,
        .flags = IORESOURCE_MEM
    },
    [4] = {
        .start = GPIO1_DR_BASE,
        .end = GPIO1_DR_BASE + REGISTER_LENGTH - 1,
        .flags = IORESOURCE_MEM
    },
};

void led_release(struct device *dev)
{
    printk("led device released!\n");
}

static struct platform_device leddevice = {
    .name = "imx6ull-led",
    .id = -1,
    // .dev = {
    //     .release = led_release,
    // },
    .num_resources = ARRAY_SIZE(led_resources),
    .resource = led_resources,
};

static int __init leddevice_init(void)
{
    return platform_device_register(&leddevice);
}

static void __exit leddevice_exit(void)
{
    platform_device_unregister(&leddevice);
}

module_init(leddevice_init);
module_exit(leddevice_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("author_name");

二、platform 驱动注册

新建文件 led-driver.c 文件,该文件提供了上层应用层所需的驱动接口,如 close 函数对应驱动中的release 接口,将在当前文件中实现。该文件最终也会被编程成 ko 模块文件,加入到 Linux 内核中。这里的逻辑基本最初学习的驱动逻辑几乎相同,如注册设备号、注册设备节点、实现 file_operations 中的文件接口。

详情可参考:字符设备驱动模板

1、注册 / 注销 platform 驱动

在 Linux 内核中使用 platform_driver 类型来表示 platform 设备,当前背景下 platform_driver 类型的实例化对象为 led_driver 

  • name:platform 驱动名称,用于和上面 platform 设备进行匹配
  • probe:platform 驱动和platform 设备匹配成功时调用,该函数中可以添加之前的 注册设备号、注册设备节点 等操作。
  • remove:platform 驱动从内核移除时调用,该函数中可以添加之前的 注销字符设备 等操作。
/* 驱动结构体 */
static struct platform_driver led_driver = {
	.driver = {
		.name = "imx6ull-led",
	},
	.probe = led_probe,
	.remove = led_remove,
};

static int __init leddriver_init(void)
{
	printk("platled driver init!\n");
	
    // 注册 platform 驱动
	return platform_driver_register(&led_driver);	
}

static void __exit leddriver_exit(void)
{
	printk("platled driver removed!\n");

    // 注销 platform 驱动
	platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);

2、加载 platform 设备资源

既然添加了 platform 设备,那就要想办法从 platform 设备中获取我们需要的寄存器信息。使用的接口为 platform_get_resource,接口原型如下: 

/**
 * @param pdev    要访问的platform设备
 * @param type    资源类型,对应resource结构体中的 flag 成员
 * @param index   访问resource数组的下标
 * @return        成功返回 resource 数组的首地址,失败返回0
 */
struct resource *platform_get_resource(struct platform_device * pdev,
					                   unsigned int type, 
                                       unsigned int index);

设备和驱动匹配成功后,probe 函数的第一个参数会传递 platform_device 指针,这个可以作为 platform_get_resource 的第一个参数。具体获取方式如下:

static int led_probe(struct platform_device *dev)
{
	uint32_t i = 0;
	uint32_t ret = 0;
	uint32_t ressize[5];                // 保存每个寄存器地址大小
	struct resource* ledresource[5];    // 获取 platform 设备中的resource数组

	/* 获取资源 */
	for (i = 0; i < 5; i++)
	{
		ledresource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);
		if (!ledresource[i])
		{
			printk("fetch resource failed\n");
			return -1;
		}
		ressize[i] = resource_size(ledresource[i]);
	}

    /* 建立物理地址和虚拟地址的映射 */
	CCM_CCGR1 = ioremap(ledresource[0]->start, ressize[0]);
	IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = ioremap(ledresource[1]->start, ressize[1]);
	IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = ioremap(ledresource[2]->start, ressize[2]);
	GPIO1_GDIR = ioremap(ledresource[3]->start, ressize[3]);
	GPIO1_DR = ioremap(ledresource[4]->start, ressize[4]);

    // ... ...
}

到此便已经和最初的 LED 驱动对接上了,重复的代码这里就不再赘述,下面提供一份完整的

3、完整 platform 驱动注册

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>			// cdev_init
#include <linux/device.h>		// device_create
#include <linux/err.h>			// IS_ERR
#include <asm/io.h>				// ioremap、iounmap
#include <linux/platform_device.h>

#define CHRDEVBASE_NAME 						"platled" 	/* 设备名 */

enum LED_STAT {
	LED_ON,
	LED_OFF
};

struct chrdev_led_t{
	struct class* class;	/* 设备节点所属类 */
	struct device* device;	/* 设备节点 */

	struct cdev dev;		/* 字符设备 */
	dev_t devid;			/* 设备号 */
	int major;				/* 主设备号 */
	int minor;				/* 次设备号 */
};

/* 寄存器虚拟地址 */
static void __iomem* CCM_CCGR1;
static void __iomem* IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03;
static void __iomem* IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03;
static void __iomem* GPIO1_GDIR;
static void __iomem* GPIO1_DR;
static u32 val;
static struct chrdev_led_t chrdev_led;

static void led_on(void)
{
	val = readl(GPIO1_DR);
	val &= ~(1 << 3);
	writel(val, GPIO1_DR);
}

static void led_off(void)
{
	val = readl(GPIO1_DR);
	val |= (1 << 3);
	writel(val, GPIO1_DR);
}

/*
 * @description 	: 打开设备
 * @param – pinode 	: 传递给驱动的 inode
 * @param - pfile 	: 设备文件,file 结构体有个叫做 private_data 的成员变量
 * 一般在 open 的时候将 private_data 指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *pinode, struct file *pfile)
{
    /* 用户实现具体功能 */
	printk("open platled driver\n");
	pfile->private_data = &chrdev_led;
    return 0;
}

/*
 * @description 	: 向设备写数据
 * @param - pfile	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 要给设备写入的数据(用户缓冲区)
 * @param - cnt 	: 要写入的数据长度
 * @param - offset	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *pfile, const char __user *buf, size_t cnt, loff_t *offset)
{
    // 获取模块数据
	struct chrdev_led_t* pdev = pfile->private_data;

	unsigned char databuf[1];
	unsigned char ledstat;
	
	// 将数据从用户缓冲区拷贝到内核缓冲区
	int ret = copy_from_user(databuf, buf, cnt);
	if(ret != 0)
		return 0;

	ledstat = buf[0] - '0';
	printk("led state: %d\n", ledstat);
	if (ledstat == LED_ON)
	{
		led_on();
	}
	else if(ledstat == LED_OFF)
	{
		led_off();
	}
    return cnt;

	return 0;
}

/*
 * @description 	: 关闭/释放设备
 * @param - pfile	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_release (struct inode *pinode, struct file * pfile)
{
    /* 用户实现具体功能 */
	printk("close chrdevbase\n");

    return 0;
}

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE, 
	.open = chrdevbase_open,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};

static int led_probe(struct platform_device *dev)
{
	uint32_t i = 0;
	uint32_t ret = 0;
	uint32_t ressize[5];
	struct resource* ledresource[5];

	/* 获取资源 */
	for (i = 0; i < 5; i++)
	{
		ledresource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);
		if (!ledresource[i])
		{
			printk("fetch resource failed\n");
			return -1;
		}
		ressize[i] = resource_size(ledresource[i]);
	}

	/* 建立物理地址和虚拟地址的映射 */
	CCM_CCGR1 = ioremap(ledresource[0]->start, ressize[0]);
	IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = ioremap(ledresource[1]->start, ressize[1]);
	IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = ioremap(ledresource[2]->start, ressize[2]);
	GPIO1_GDIR = ioremap(ledresource[3]->start, ressize[3]);
	GPIO1_DR = ioremap(ledresource[4]->start, ressize[4]);

	/* GPIO1 时钟源初始化 */
	val = readl(CCM_CCGR1);
	val |= (3 << 26);
	writel(val, CCM_CCGR1);

	/* IO复用 */
	writel(5, IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03);

	/* IO复用引脚初始化 */
	writel(0x10B0, IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03);
	
	/* GPIO1_IO03 输出使能 */
	val = readl(GPIO1_GDIR);
	val |= (1 << 3);
	writel(val, GPIO1_GDIR);

	/* LED 熄灭(GPIO1的第3个引脚为高电平) */
	val = readl(GPIO1_DR);
	val |= (1 << 3);
	writel(val, GPIO1_DR);

	/* 1. 注册设备号 */
	if (chrdev_led.major)
	{
		chrdev_led.devid = MKDEV(chrdev_led.major, 0);
		ret = register_chrdev_region(chrdev_led.devid, 1, CHRDEVBASE_NAME);
	}
	else
	{
		ret = alloc_chrdev_region(&chrdev_led.devid, 0, 1, CHRDEVBASE_NAME);
		chrdev_led.major = MAJOR(chrdev_led.devid);
		chrdev_led.minor = MINOR(chrdev_led.devid);
	}

	/* 2. 初始化字符设备 */
	chrdev_led.dev.owner = THIS_MODULE;
	cdev_init(&chrdev_led.dev, &chrdevbase_fops);					// 初始化字符设备
	/* 3. 将字符设备添加到内核 */
	cdev_add(&chrdev_led.dev, chrdev_led.devid, 1);			// 将字符设备添加到内核

	/* 自动创建设备节点 */
	// 设备节点所属类
 	chrdev_led.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
	if (IS_ERR(chrdev_led.class))
	{
		return PTR_ERR(chrdev_led.class);
	}
	// 创建设备节点
	chrdev_led.device = device_create(chrdev_led.class, NULL, chrdev_led.devid, NULL, CHRDEVBASE_NAME);
	if (IS_ERR(chrdev_led.device))
	{
		return PTR_ERR(chrdev_led.device);
	}
	
	printk("chrdevbase init!\n");
	return 0;
}

static int led_remove(struct platform_device *dev)
{
	/* 取消虚拟地址和物理地址的映射 */
	iounmap(CCM_CCGR1);
	iounmap(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03);
	iounmap(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03);
	iounmap(GPIO1_GDIR);
	iounmap(GPIO1_DR);
	
	/* 注销字符设备 */
	unregister_chrdev_region(chrdev_led.devid, 1);		// 注销设备号
	cdev_del(&chrdev_led.dev);							// 卸载字符设备
	
	device_destroy(chrdev_led.class, chrdev_led.devid);	// 删除节点
	class_destroy(chrdev_led.class);					// 删除类

	return 0;
}

/* 驱动结构体 */
static struct platform_driver led_driver = {
	.driver = {
		.name = "imx6ull-led",
	},
	.probe = led_probe,
	.remove = led_remove,
};

static int __init leddriver_init(void)
{
	printk("platled driver init!\n");
	
	return platform_driver_register(&led_driver);	
}

static void __exit leddriver_exit(void)
{
	printk("platled driver removed!\n");

	platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("author_name");

三、Makefile 编译

Makefile 的编译也和之前有所不同,这里提供一份编译多个模块的 Makefile 脚本,MODULE_NAME 变量是创建的两个 c 文件名,main.c 是测试程序,这个就参考之前的了,生成的执行文件为 testApp 。

ARCH				:= arm
CROSS_COMPILE		:= arm-linux-gnueabihf-
CC					:= $(CROSS_COMPILE)gcc

KERNEL_DIR  		:= /home/gzx/IMX6ULL/kernel/4.1.15/linux-imx-4.1.15-source-compiled
CURRENR_DIR			:= $(shell pwd)

MODULE_NAME			:= led-device led-driver
obj-m				+= $(foreach subitem,${MODULE_NAME},$(subitem).o)
kofile				= $(patsubst %.o,%.ko,${obj-m})

build: kernel_modules
kernel_modules:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURRENR_DIR) modules
	$(CC) -o testApp main.c     

.PHONY:clean
clean:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURRENR_DIR) clean
	@rm testApp
  • 31
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值