嵌入式Linux设备驱动程序开发指南9(平台设备驱动)——读书笔记

平台设备驱动

  • 九、平台设备驱动
    • 9.1 平台设备驱动概述
    • 9.2 GPIO驱动
      • 9.2.1 简介
      • 9.2.2 硬件名称
      • 9.2.3 引脚控制器
      • 9.2.4 引脚控制子系统
      • 9.2.5 GPIO控制器驱动
    • 9.3 RGB LED平台设备模块
      • 9.3.1 简介
      • 9.3.2 设备树
      • 9.3.3 ledRGB代码分析:
      • 9.3.4 RGBled全部代码

九、平台设备驱动

9.1 平台设备驱动概述

在嵌入式系统中,设备通常并不通过总线连接,将字符设备转为平台设备。在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

9.2 GPIO驱动

9.2.1 简介

在开发驱动时候,需要操作不同硬件,需要使用处理器外设寄存器,参考的文档有:

1、Applications Processor Reference Manial
2、SAMA5D2 Series Datasheet
3、Xplained Ultra User Guide
4、硬件原理图

9.2.2 硬件名称

焊点			:印在电路板上的裸片的特定表面,如D12焊点;
引脚复用			:单个引脚在内部进行复杂配置实现不同的功能;
逻辑/规范名称	:通常对应于焊点的首要功能;
网络标号			:描述功能线的实际用途;

9.2.3 引脚控制器

参阅datasheet.

9.2.4 引脚控制子系统

实现在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

9.2.5 GPIO控制器驱动

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地址映射,如下图:
嵌入式Linux设备驱动程序开发指南9(平台设备驱动)——读书笔记_第1张图片

9.3 RGB LED平台设备模块

9.3.1 简介

通过控制几个LED灯,将多个Soc外设寄存器地址从物理地址映射到虚拟机地址,并且使用杂项框架为每个LED创建字符设备(misc),通过write和read来调用控制LED在内核态和用户态之间的数据交换。
SAMA5D2处理器有四组I/O,分别是PA\PB\PC\PD,PIO每条I/O线均具有常见的GPIO操作功能,如配置IO输入输出,配置上下拉,输入模式的触发模式,

9.3.2 设备树

/*
 * 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";
		};
	};

9.3.3 ledRGB代码分析:

驱动包含的头文件:

#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,
	}
};

9.3.4 RGBled全部代码

#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

你可能感兴趣的:(linux驱动,驱动开发,驱动程序,linux)