在嵌入式系统中,设备通常并不通过总线连接,将字符设备转为平台设备。在Linux系统中有一类特殊总线被构建被称为平台总线,平台设备驱动是被静态枚举的,而非动态发现。
驱动定义了platform_driver数据结构使用举例:
/* Define platform driver structure */
static struct platform_driver my_platform_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "hellokeys",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
...
platform_driver_register(&my_platform_driver)
修改设备树,目录如下:
/arch.arn.boot/dts/
添加内容如下:
hellkeys{
compatible = "arrow, hellokeys";
};
hellokeys_sam.c平台设备代码:
定义了字符设备驱动、杂项字符设备驱动、平台设备驱动:
#include
#include
#include
#include
static int my_dev_open(struct inode *inode, struct file *file)
{
pr_info("my_dev_open() is called.\n");
return 0;
}
static int my_dev_close(struct inode *inode, struct file *file)
{
pr_info("my_dev_close() is called.\n");
return 0;
}
static long my_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
pr_info("my_dev_ioctl() is called. cmd = %d, arg = %ld\n", cmd, arg);
return 0;
}
static const struct file_operations my_dev_fops = {
.owner = THIS_MODULE,
.open = my_dev_open,
.release = my_dev_close,
.unlocked_ioctl = my_dev_ioctl,
};
static struct miscdevice helloworld_miscdevice = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mydev",
.fops = &my_dev_fops,
};
/* Add probe() function */
static int __init my_probe(struct platform_device *pdev)
{
int ret_val;
pr_info("my_probe() function is called.\n");
ret_val = misc_register(&helloworld_miscdevice);
if (ret_val != 0) {
pr_err("could not register the misc device mydev");
return ret_val;
}
pr_info("mydev: got minor %i\n",helloworld_miscdevice.minor);
return 0;
}
/* Add remove() function */
static int __exit my_remove(struct platform_device *pdev)
{
pr_info("my_remove() function is called.\n");
misc_deregister(&helloworld_miscdevice);
return 0;
}
/* Declare a list of devices supported by the driver */
static const struct of_device_id my_of_ids[] = {
{ .compatible = "arrow,hellokeys"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);
/* Define platform driver structure */
static struct platform_driver my_platform_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "hellokeys",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
/* Register our platform driver */
module_platform_driver(my_platform_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(" ");
MODULE_DESCRIPTION("This is the simplest platform driver");
测试调试:
insmod hellkey_imx.ko
find /sys -name "hellokeys"
ls -l /sys/bus/platform/drivers/hellokeys
ls -l /sys/module/hellykeys_imx/drivers
ls -l /sys/class/misc
ls -l /dev
rmmod hellkey_imx.ko
在开发驱动时候,需要操作不同硬件,需要使用处理器外设寄存器,参考的文档有:
1、Applications Processor Reference Manial
2、SAMA5D2 Series Datasheet
3、Xplained Ultra User Guide
4、硬件原理图
焊点 :印在电路板上的裸片的特定表面,如D12焊点;
引脚复用 :单个引脚在内部进行复杂配置实现不同的功能;
逻辑/规范名称 :通常对应于焊点的首要功能;
网络标号 :描述功能线的实际用途;
参阅datasheet.
实现在driver/pinctl中,如pinctl-imx7d.c
设备树提供引脚、节点配置,pinctl驱动(/drivers/pinctl-imx.c)列出引脚和引脚组、配置引脚,gpio驱动实现gpiochip(driver/gpio)和irq_gpio(kernel/irq),pinctl子系统核心(请求引脚服用设备驱动)。
解析设备树的函数:
imx_pinctl_probe_dt()
引脚复用文件:
drivers/pinctl/pinmux.c
GPIO控制器最主要数据结构是gpiochip,定义在:
include/linux/gpio/driver.h
GPIO控制器还提供了中断,头文件如下:
#include
GPIO中断芯片通常分类:
1、链式GPIO中断芯片
chained_irq_enter()
generic_handle_irq()
chained_irq_exit()
2、通用链式GPIO中断芯片
generic_handle_irq()
3、嵌套的线程化GPIO中断芯片
片外GPIO扩展,
handle_nested_irq()
使用GPIO:
gpio_direction_input()
gpio_direction_output()
gpio_get_value()
gpio_set_value()
gpio_to_irq() //获取给定的GPIO对应的Linux IO号。
内核态与用户态交换数据:
当进程执行系统调用时,内核将在调用者的进程上下文中执行;
当内核响应中断时,内核中断处理程序将异步运行在中断上下文。
1、单变量访问
get_user() //kernel -> app
put_user() //app -> kernel
2、数组访问
copy_to_user() //kernel -> app
copy_from_user() //app -> kernel
内存IO映射
外围设备的控制是通过操作其寄存器实现,设备具有多个寄存器,通过内存地址空间(MMIO)或者I/O地址空间(PIO)的连续地址来访问这些设备寄存器。
Linux驱动程序无法直接访问物理I/O地址,而是需要MMU映射。
将I/O内存映射到虚拟内存方法:
ioremap()
iounmap()
或
#include
devm_ioremap()
devm_ioremap()
物理地址即外设寄存器地址,要映射到虚拟地址,SAMA5D2地址映射,如下图:
通过控制几个LED灯,将多个Soc外设寄存器地址从物理地址映射到虚拟机地址,并且使用杂项框架为每个LED创建字符设备(misc),通过write和read来调用控制LED在内核态和用户态之间的数据交换。
SAMA5D2处理器有四组I/O,分别是PA\PB\PC\PD,PIO每条I/O线均具有常见的GPIO操作功能,如配置IO输入输出,配置上下拉,输入模式的触发模式,
/*
* at91-sama5d2_xplained_common.dtsi - Device Tree file for SAMA5D2 Xplained board
*
* Copyright (C) 2016 Atmel,
* 2016 Nicolas Ferre <[email protected]>
* 2016 Ludovic.Desroches <[email protected]>
*
* This file is dual-licensed: you can use it either under the terms
* of the GPL or the X11 license, at your option. Note that this dual
* licensing only applies to this file, and not this project as a
* whole.
*
#include "sama5d2.dtsi"
//#include "at91-sama5d2_xplained_ov7670.dtsi"
#include "sama5d2-pinfunc.h"
#include
#include
#include
pinctrl_led_gpio_default: led_gpio_default {
pinmux = <PIN_PB0__GPIO>,
<PIN_PB5__GPIO>,
<PIN_PB6__GPIO>;
bias-pull-up;
};
/ {
model = "Atmel SAMA5D2 Xplained";
compatible = "atmel,sama5d2-xplained", "atmel,sama5d2", "atmel,sama5";
chosen {
stdout-path = "serial0:115200n8";
};
ledred {
compatible = "arrow,RGBleds";
label = "ledred";
pinctrl-0 = <&pinctrl_led_gpio_default>;
};
ledgreen {
compatible = "arrow,RGBleds";
label = "ledgreen";
};
ledblue {
compatible = "arrow,RGBleds";
label = "ledblue";
};
ledclassRGB {
compatible = "arrow,RGBclassleds";
reg = <0xFC038000 0x4000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led_gpio_default>;
status = "okay";
red {
label = "red";
};
green {
label = "green";
};
blue {
label = "blue";
linux,default-trigger = "heartbeat";
};
};
驱动包含的头文件:
#include
#include /* struct file_operations */
/* platform_driver_register(), platform_set_drvdata() */
#include
#include /* devm_ioremap(), iowrite32() */
#include /* of_property_read_string() */
#include /* copy_from_user(), copy_to_user() */
#include /* misc_register() */
定义寄存器掩码:
/* Declare masks to configure the different registers */
#define PIO_PB0_MASK (1 << 0) /* blue */
#define PIO_PB5_MASK (1 << 5) /* green */
#define PIO_PB6_MASK (1 << 6) /* red */
#define PIO_CFGR1_MASK (1 << 8) /* masked bits direction (output), no PUEN, no PDEN */
#define PIO_MASK_ALL_LEDS (PIO_PB0_MASK | PIO_PB5_MASK | PIO_PB6_MASK)
定义物理寄存器地址:
/* Declare physical addresses */
static int PIO_SODR1 = 0xFC038050;
static int PIO_CODR1 = 0xFC038054;
static int PIO_MSKR1 = 0xFC038040;
static int PIO_CFGR1 = 0xFC038044;
申明devm_iomem指针,用于存放devm_ioremap()返回的虚拟地址。
定义私有数据结构:
保存每个设备特定信息
/* declare a private structure */
struct led_dev
{
struct miscdevice led_misc_device; /* assign device for each led */
u32 led_mask; /* different mask if led is R,G or B */
const char *led_name; /* assigned value cannot be modified */
char led_value[8];
};
file_operation:
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.read = led_read,
.write = led_write,
};
misc杂项定义:
led_device->led_misc_device.fops = &led_fops;
led_write:
iowrite32()写寄存器。
static ssize_t led_write(struct file *file, const char __user *buff,
size_t count, loff_t *ppos)
{
const char *led_on = "on";
const char *led_off = "off";
struct led_dev *led_device;
pr_info("led_write() is called.\n");
led_device = container_of(file->private_data,
struct led_dev, led_misc_device);
/*
* terminal echo add \n character.
* led_device->led_value = "on\n" or "off\n after copy_from_user"
* count = 3 for "on\n" and 4 for "off\n"
*/
if(copy_from_user(led_device->led_value, buff, count)) {
pr_info("Bad copied value\n");
return -EFAULT;
}
/*
* Replace \n for \0 in led_device->led_value
* char array to create a char string
*/
led_device->led_value[count-1] = '\0';
pr_info("This message is received from User Space: %s\n",
led_device->led_value);
/* compare strings to switch on/off the LED */
if(!strcmp(led_device->led_value, led_on)) {
iowrite32(led_device->led_mask, PIO_CODR1_W);
}
else if (!strcmp(led_device->led_value, led_off)) {
iowrite32(led_device->led_mask, PIO_SODR1_W);
}
else {
pr_info("Bad value\n");
return -EINVAL;
}
pr_info("led_write() is exit.\n");
return count;
}
申明与设备树匹配的compatible:
static const struct of_device_id my_of_ids[] = {
{ .compatible = "arrow,RGBleds"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);
static struct platform_driver led_platform_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "RGBleds",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
#include
#include
#include
#include
#include
#include
#define PIO_SODR1_offset 0x50
#define PIO_CODR1_offset 0x54
#define PIO_CFGR1_offset 0x44
#define PIO_MSKR1_offset 0x40
#define PIO_PB0_MASK (1 << 0)
#define PIO_PB5_MASK (1 << 5)
#define PIO_PB6_MASK (1 << 6)
#define PIO_CFGR1_MASK (1 << 8)
#define PIO_MASK_ALL_LEDS (PIO_PB0_MASK | PIO_PB5_MASK | PIO_PB6_MASK)
struct led_dev
{
u32 led_mask; /* different mask if led is R,G or B */
void __iomem *base;
struct led_classdev cdev;
};
static void led_control(struct led_classdev *led_cdev, enum led_brightness b)
{
struct led_dev *led = container_of(led_cdev, struct led_dev, cdev);
iowrite32(PIO_MASK_ALL_LEDS, led->base + PIO_SODR1_offset);
if (b != LED_OFF) /* LED ON */
iowrite32(led->led_mask, led->base + PIO_CODR1_offset);
else
iowrite32(led->led_mask, led->base + PIO_SODR1_offset); /* LED OFF */
}
static int __init ledclass_probe(struct platform_device *pdev)
{
void __iomem *g_ioremap_addr;
struct device_node *child;
struct resource *r;
struct device *dev = &pdev->dev;
int count, ret;
dev_info(dev, "platform_probe enter\n");
/* get our first memory resource from device tree */
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r) {
dev_err(dev, "IORESOURCE_MEM, 0 does not exist\n");
return -EINVAL;
}
dev_info(dev, "r->start = 0x%08lx\n", (long unsigned int)r->start);
dev_info(dev, "r->end = 0x%08lx\n", (long unsigned int)r->end);
/* ioremap our memory region */
g_ioremap_addr = devm_ioremap(dev, r->start, resource_size(r));
if (!g_ioremap_addr) {
dev_err(dev, "ioremap failed \n");
return -ENOMEM;
}
count = of_get_child_count(dev->of_node);
if (!count)
return -EINVAL;
dev_info(dev, "there are %d nodes\n", count);
/* Enable all leds and set dir to output */
iowrite32(PIO_MASK_ALL_LEDS, g_ioremap_addr + PIO_MSKR1_offset);
iowrite32(PIO_CFGR1_MASK, g_ioremap_addr + PIO_CFGR1_offset);
/* Switch off all the leds */
iowrite32(PIO_MASK_ALL_LEDS, g_ioremap_addr + PIO_SODR1_offset);
for_each_child_of_node(dev->of_node, child){
struct led_dev *led_device;
struct led_classdev *cdev;
led_device = devm_kzalloc(dev, sizeof(*led_device), GFP_KERNEL);
if (!led_device)
return -ENOMEM;
cdev = &led_device->cdev;
led_device->base = g_ioremap_addr;
of_property_read_string(child, "label", &cdev->name);
if (strcmp(cdev->name,"red") == 0) {
led_device->led_mask = PIO_PB6_MASK;
led_device->cdev.default_trigger = "heartbeat";
}
else if (strcmp(cdev->name,"green") == 0) {
led_device->led_mask = PIO_PB5_MASK;
}
else if (strcmp(cdev->name,"blue") == 0) {
led_device->led_mask = PIO_PB0_MASK;
}
else {
dev_info(dev, "Bad device tree value\n");
return -EINVAL;
}
/* Disable timer trigger until led is on */
led_device->cdev.brightness = LED_OFF;
led_device->cdev.brightness_set = led_control;
ret = devm_led_classdev_register(dev, &led_device->cdev);
if (ret) {
dev_err(dev, "failed to register the led %s\n", cdev->name);
of_node_put(child);
return ret;
}
}
dev_info(dev, "leds_probe exit\n");
return 0;
}
static int __exit ledclass_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "leds_remove enter\n");
dev_info(&pdev->dev, "leds_remove exit\n");
return 0;
}
static const struct of_device_id my_of_ids[] = {
{ .compatible = "arrow,RGBclassleds"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);
static struct platform_driver led_platform_driver = {
.probe = ledclass_probe,
.remove = ledclass_remove,
.driver = {
.name = "RGBclassleds",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
static int ledRGBclass_init(void)
{
int ret_val;
pr_info("demo_init enter\n");
ret_val = platform_driver_register(&led_platform_driver);
if (ret_val !=0)
{
pr_err("platform value returned %d\n", ret_val);
return ret_val;
}
pr_info("demo_init exit\n");
return 0;
}
static void ledRGBclass_exit(void)
{
pr_info("led driver enter\n");
platform_driver_unregister(&led_platform_driver);
pr_info("led driver exit\n");
}
module_init(ledRGBclass_init);
module_exit(ledRGBclass_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(" ");
MODULE_DESCRIPTION("This is a driver that turns on/off RGB leds \
using the LED subsystem");
测试调试:
insmod ledRGB_sam_platform.ko
ls /dev/led*
echo on > /dev/ledblue
echo on > /dev/ledred
echo on > /dev/ledgreen
echo off > /dev/ledgreen
cat /dev/ledred
rmmod ledRGB_sam_platform.ko
感谢阅读,祝君成功!
-by aiziyou