linux常见驱动修改

=============================== 说明 ===============================
本文以A5为例,举8种我们公司常用接口的极度精简的驱动程序,只宜参考,使用时请自行补全纠错逻辑和驱动框架
内容如下:
1、gpio
2、外部中断
3、leds
4、uart
5、i2c
6、spi
7、pck
8、gadget

=============================== gpio ===============================
GPIO不需要在设备树中进行额外配置,A5启动时所有引脚的默认工作模式均是GPIO。GPIO的应用层接口是/sys/class/gpio。

1.gpio应用层接口的结构
/sys/class/gpio
┣ export # 申请引脚的接口
┣ unexport # 取消申请引脚的接口
┣ pioA15 # 单个引脚设备
┃ ┣ direction # 输出/输入控制
┃ ┣ value # 电平
┃ ┗ ......
┗ ......

2.使用方法
1)设置输入
设置B22引脚为输入并读取数值,例:
cd /sys/class/gpio
echo 54 > export # gpioA口为0号bank,54 = B*32 + 22
cd pioB22
echo in > direction # 字符串 "in"
cat value # 字符 ‘0‘ 或 ‘1‘
如果是在应用程序中使用,那么cat可以替换为read函数,echo可以替换成write函数。
2)设置输出
设置C1引脚为输出并设置电平:
cd /sys/class/gpio
echo 65 > export
cd pioC1
echo out > direction
echo 1 > value
echo 0 > value

=========================== 外部中断 & key ===========================
外部中断有两种实现方式,纯驱动方式与key方式

1.纯驱动方式
假如要添加名为btn_test的按键驱动程序,当连接pioE0的按键按下时,打印"User button pressed!\n"
1)设备树修改,添加以下节点
/ {
ahb {
apb {
[email protected] {
board {
/* 声明需要使用的pin脚 */
pinctrl_btn_test_gpio: btn_test_gpio {
atmel,pins =
<AT91_PIOE 0 AT91_PERIPH_GPIO AT91_PINCTRL_PULL_UP>;
};
};
};
};
};
btn_test {
/* 驱动匹配符 */
compatible = "btn_test";
status = "okay";
/* 声明所需要使用的pin脚 */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_btn_test_gpio>;
};
};

2)驱动程序的配置,此处仅给出probe函数的实现
#define GPIO_TO_PIN(port, pin) ((port) * 32 + pin)
#define BTN_PIN (GPIO_TO_PIN(4, 0))

// interrupt function ======================================================

static irqreturn_t BtnTest_Interrupt(int irq, void *dev_id)
{
/* 打印信息 */
printk(KERN_INFO "User button pressed!\n");
/* 返回值:中断正常退出 */
return IRQ_RETVAL(IRQ_HANDLED);
}

// probe & remove ==========================================================

static int BtnTest_Probe(struct platform_device *pdev)
{
struct pinctrl *pinctrl;
int ret = 0;
/* 初始化pin脚,此处需要依赖于设备树的 pinctrl-names 属性、pinctrl-0 属性 */
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
....
/* 申请中断号以及配置中断触发条件 */
ret = request_irq(gpio_to_irq(BTN_PIN), BtnTest_Interrupt,
IRQ_TYPE_EDGE_FALLING, "user key", NULL);

return ret;
}

=============================== leds ===============================
leds驱动是gpio驱动的变体

1.应用层接口
/sys/class/leds
┣ led_1 # 单个led设备
┃ ┣ brightness # 亮度控制,可以为‘0‘ 或 ‘1‘
┃ ┗ ......
┗ ......

1)点亮led灯
echo 1 > /sys/class/leds/led_1/brightness

2.驱动实现
leds的驱动非常简单,它是gpio的一种变体,只需要修改设备树就可以实现了,
添加以下节点:
/ {
leds {
compatible = "gpio-leds";

/* leds 设备对象 */
led_1 {
label = "led_1"; /* 应用层接口的名称 */
gpios = <&pioA 20 GPIO_ACTIVE_HIGH>; /* ACTIVE项为HIGH,则1表示高电平 */
};

......
};
};

=============================== uart ===============================
uart是linux中最常见的接口,驱动程序十分完善,一般在应用层操作即可

例:
// 打开串口,设置波特率115200,无校验位无停止位
int OpenUart(char *path)
{
int serial = 0;
struct termios options;
bzero(&options, sizeof(options));

//打开串口的例行操作
serial = open(path, O_RDWR | O_NONBLOCK);
tcflush(serial, TCIOFLUSH);
options.c_cflag &= ~CSIZE;
options.c_cflag |= (CLOCAL | CREAD);

// 配置选项 --- 可以随便修改
cfsetispeed(&options, B115200); // 接收速率
cfsetospeed(&options, B115200); // 发送速率
options.c_cflag |= CS8; // 8数据位
options.c_cflag &= ~PARENB; // 无校验
options.c_cflag &= ~CSTOPB; // 1停止位

// 消除一切特殊的软硬件流控制
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG | CRTSCTS);
options.c_oflag &= ~(OPOST);

// 设置超时范围
options.c_cc[VTIME] = 150;
options.c_cc[VMIN] = 0;

// 写入配置
tcsetattr(serial, TCSANOW, &options);
tcflush(serial, TCIOFLUSH);

return serial;
}

读写则直接使用read和write

================================ i2c ================================
断网了。。。待完善

================================ spi ================================
linux源码中有一个非常优秀的spi接口驱动程序——spidev,这是我司规定的SPI标准接口

1、修改设备树
spi在sama5d3.dtsi中的声明如下:
spi1: [email protected] {
#address-cells = <1>;
#size-cells = <0>;
compatible = "atmel,at91rm9200-spi";
reg = <0xf8008000 0x100>;
interrupts = <25 IRQ_TYPE_LEVEL_HIGH 3>;
dmas = <&dma1 2 AT91_DMA_CFG_PER_ID(15)>,
<&dma1 2 AT91_DMA_CFG_PER_ID(16)>;
dma-names = "tx", "rx";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_spi1>;
clocks = <&spi1_clk>;
clock-names = "spi_clk";
status = "disabled";
};

那么我们自己的dts文件在继承 sama5d3.dtsi 这个文件之后,不妨作一些小调整:
spi1: [email protected] {
dmas = <0>, <0>; /* 禁用DMA */
pinctrl-0 = <&pinctrl_spi1 &pinctrl_spi1_cs3>; /* 增加cs脚的复用 */

[email protected] {
compatible = "rohm,dh2228fv"; /* 重要的compatible属性,这是从spidev.c里面抠出来的 */
spi-max-frequency = <4000000>; /* 额定速度,因设备而异 */
reg = <3>; /* cs3 pin */
spi-cpha; /* cpha与cpol属性,可选,但这个选择很重要,一定要和设备吻合!否则会导致时序异常 */
spi-cpol; /* 关于cpha和cpol的配置,请度娘“SPI的四种模式”*/
};

status = "okay";
};

2、内核调整
menuconfig里面的 User mode SPI device driver support 一定要选上,这个选项直接和spidev.c关联
选了,设备树改好,烧写系统,在/dev 里面会出现一个spidev的设备

3、使用
我们是在应用层操作这个接口的,由于我们已经在设备树中存放了足够的信息,这个例子可以变得更简单,不过还是给出完整的过程
例子如下:

1)打开spi
int OpenSpi(char *path)
{
int spidev = 0;
unsigned char mode = SPI_MODE_3; // 设置工作模式

// 打开SPI并配置
spidev = open(path, O_RDWR);
ioctl(spidev, SPI_IOC_RD_MODE, &mode); //允许读
ioctl(spidev, SPI_IOC_WR_MODE, &mode); //允许写
ioctl(spidev, SPI_IOC_WR_MAX_SPEED_HZ, 4000000); //设置额定速率

return spidev;
}

2)读写数据
spi通常要面对的是这样一种情况:先发送一个地址,然后再读取;

例子:
int SPI_ReadReg(unsigned char reg_addr, char *buff, int reg_size)
{
// 读和写拆成两截buff
struct spi_ioc_transfer spi_buffer[2];
int ret = 0;

bzero(spi_buffer, sizeof(spi_buffer));

// 第一个buffer结构体长度为1,只写,存入地址
spi_buffer[0].bits_per_word = 8;
spi_buffer[0].len = 1;
spi_buffer[0].tx_buf = &reg_addr;
spi_buffer[0].cs_change = 0;

// 第二个buffer结构体长度为读出数据的长度,只读,把buff传进去存放返回值
spi_buffer[1].bits_per_word = 8;
spi_buffer[1].len = size;
spi_buffer[1].rx_buf = buff;
spi_buffer[1].cs_change = 0;

// 执行传输命令,结果就会存放到buff里面
ret = ioctl(spidev, SPI_IOC_MESSAGE(2), spi_buffer);
return ret;
}

实际上也是可以同时读写的,即是一个结构体就能搞定很多事情,看程序怎么写了

================================ pck ================================
sama5d3里面pck指时钟输出,可以使用在多种场合,为设备提供时钟信号

1)设备树
其实大体上和外部中断是一样的,只是引脚的复用功能配置有区别,注意 AT91_PERIPH_B:

pinctrl_pck_gpio: pck_gpio {
atmel,pins = <AT91_PIOD 30 AT91_PERIPH_B AT91_PINCTRL_PULL_UP>;
};

2)驱动
启用pck,如果想要最简洁的话,那么直接在probe里面把这个频率固死就可以了(大概也不会手贱到改频率?很危险的哦~)

在引脚输出时钟,例子:
int Probe(struct platform_device *dev)
{
int clk_src = 0;
int clk_rate = 0;
struct clk *mclk = NULL;

...... // 省略获得引脚的过程

// 首先取得一个叫main的内部时钟,这是在sama5d3片内集成的
clk_src = clk_get(NULL, "main");
clk_rate = clk_get_rate(clk_src);

// 配置pck0引脚相连的时钟控制器
clk_get(NULL, "pck0"); // 获得pck0时钟
clk_set_parent(mclk, clk_src); // 将main作为pck0的输入源,前提是硬件支持,否则会失败
clk_set_rate(mclk, MCLK_RATE); // 尝试调制pck0的速率到与MCLK_RATE最接近的数值

return clk_prepare_enable(mclk); // 开启时钟
}

============================== gadget ==============================

gadget是USB的一种工作模式,可以让设备变成U盘,或者串口

主要是一些配置工作,不需要写代码

1、内核menuconfig

1)进入 Device Drivers -> USB support,选择
USB Modem (CDC ACM) support
USB Mass Storage support
USB Serial Converter support
[*] USB Generic Serial Driver
USB Gadget Support
Mass Storage Gadget
Serial Gadget (with CDC ACM and CDC OBEX support)
CDC Composite Device (ACM and mass storage) (这个是重点,要选择m)
Multifunction Composite Gadget

2)把drivers/usb下的所有.ko搬到开发板,并modprobe

3)最后modprobe的g_acm_ms.ko有点不一样:
insmod g_acm_ms.ko file=/mnt/static.img luns=1 ro=0 stall=0 removable=1 iSerialNumber=3000111 iProduct=zhdgnss iManufacturer=zhd_survey

时间: 2024-08-07 06:14:16

linux常见驱动修改的相关文章

让你提前认识软件开发(51):VC++集成开发环境中Linux下Pclint工程的配置方法及常见错误修改

第3部分 软件研发工作总结 VC++集成开发环境中Linux下Pclint工程的配置方法及常见错误修改 [文章摘要] Pclint是一种C/C++软件代码静态分析工具.它是一种更加严格的编译器,能够发现普通编译器所不能发现的代码中的很多问题,因此被广泛应用于软件开发项目中. 本文介绍了如何在VC++集成开发环境中配置Linux下的Pclint工程,给出了C语言中pclint规则A检查的常见错误,并描述了对应的修改办法. [关键词] VC++  Pclint  配置  操作  修改 1. 前言 P

Linux设备驱动开发基础

1.驱动概述和开发环境搭建 1.1驱动设备的作用 对设备驱动最通俗的解释就是"驱动硬件设备行动".驱动与底层硬件直接打交道,按照硬件设备的具体工作方式,读写设备的寄存器,完成设备的轮训.中断处理.DMA通信,进行物理内存向虚拟内存的映射等,最终让通信设备能收发数据,让显示设备能显示文字和画面,让存储设备能记录文件和数据. 由此可见,设备驱动充当了硬件和应用软件之间的纽带,他使得应用软件只需要调用系统软件的应用编程接口(API)就可让硬件去完成要求的工作.在系统中没有操作系统的情况下,工

linux设备驱动归纳总结(九):1.platform总线的设备和驱动【转】

本文转载自:http://blog.chinaunix.net/uid-25014876-id-111745.html linux设备驱动归纳总结(九):1.platform总线的设备和驱动 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 这一节可以理解是第八章的延伸,从这节开始介绍platform设备驱动. xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

【转】深入浅出:Linux设备驱动之字符设备驱动

深入浅出:Linux设备驱动之字符设备驱动 一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流的设备,常见的字符设备有鼠标.键盘.串口.控制台和LED设备等. 块设备:是指可以从设备的任意位置读取一定长度数据的设备.块设备包括硬盘.磁盘.U盘和SD卡等. 每一个字符设备或块设备都在/dev目录下对应一个设备文件.linux用户程序通过设备文件(或称

Hasen的linux设备驱动开发学习之旅--时钟

/** * Author:hasen * 参考 :<linux设备驱动开发详解> * 简介:android小菜鸟的linux * 设备驱动开发学习之旅 * 主题:时钟 * Date:2014-11-15 */ 一.内核定时器 1.内核定时器编程 软件意义上的定时器最终依赖硬件定时器来是实现,内核在时钟中断发生后执行检测各定时器是否到期, 到期后的定时器处理函数将作为软中断在底半部执行.实质上,时钟中断处理程序会唤起TIMER_SOFTIRQ 软中断,运行当前处理器上到期的所有定时器. Linu

Linux设备驱动之并发控制

Linux 设备驱动中必须解决的一个问题是多个进程对共享资源的并发访问,并发的访问会导致竞态. 中断屏蔽.原子操作.自旋锁和信号量都是解决并发问题的机制.中断屏蔽很少单独被使用,原子操作只能针对整数进行,因此自旋锁和信号量应用最为广泛. 自旋锁会导致死循环,锁定期间不允许阻塞,因此要求锁定的临界区小.信号量允许临界区阻塞,可以适用于临界区大的情况. 读写自旋锁和读写信号量分别是放宽了条件的自旋锁和信号量,它们允许多个执行单元对共享资源的并发读. 中断屏蔽 访问共享资源的代码区域称为临界区( cr

Linux网络设备驱动结构概述

网络设备驱动相比字符型设备的驱动要复杂一些,除了总体上驱动的框架有一些相似外,有很多地方都是不同,但网络设备驱动有一个很大的特点就是有固定的框架可以遵循,具体的框架会在后边详细的叙述.1.网络协议接口层在网络协议接口层,只提供了两个抽象函数dev_queue_xmit()与 netif_rx(),之所以称之为抽象函数,是因为这两个函数抽象了很多底层的操作,不管是那个芯片它在网络协议结构的操作函数都是这两个函数,采用这样的抽象后,给上层带来了很多的方便,给上层协议提供统一的数据包收发接口,无论上层

Linux设备驱动核心理论(一)

4.Linux内核模块 4.1 Linux内核模块简介 如果把所有需要的功能都编译到Linux内核.这回导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,将不得不重新编译内核. 现在我们需要的是一种机制使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,其对应的代码被动态地加载到内核中. Linux提供了这样的一种机制,这种机制被称为模块(Module).模块具有这样的特点: 模块本身不被编译如内核映像,从而控制内核的大小. 模块一旦被加载,它就

Linux设备驱动核心理论(二)

7.Linux设备驱动中的并发控制 7.1 并发与竞态 并发(concurrency)指的是多个执行单元同时.并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量.静态变量等)的访问则很容易导致竞态(race conditions). 1.对称多处理器(SMP)的多个CPU SMP是一种紧耦合.共享存储的系统模型,其体系结构如下图,它的特点是多个CPU使用共同的系统总线,因此可访问共同的外设和存储器. 2.单CPU内进程与抢占它的进程 Linux 2.6内核支持抢占调度,一个进程内