=============================== 说明 ===============================
本文以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 = ®_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