# SPI 驱动编写
这里主要实现SPI的设备驱动。
编写一个spidbg驱动,用来实现对spi总线的debug。(其实就是从spidev精简而来)
## SPIDBG 驱动编写
### 代码环境
在staging中添加spidbg目录,并修改上级的Makefile和Kconfig
添加Makefile:
~~~
obj-$(CONFIG_SPIDBG) := spidbg.o
ccflags-y += -I$(srctree)/$(src)/include
~~~
添加Kconfig:
~~~
config SPIDBG
tristate "SPI DBG driver"
---help---
SPIDBG
~~~
然后在menuconfig里选中驱动
### 设备树编写
建立新dts:sun8i-v3s-licheepi-zero-driver.dts
~~~
/dts-v1/;
#include "sun8i-v3s-licheepi-zero.dts"
/{
chosen {
/delete-node/ framebuffer@0;
};
};
&spi0 {
pinctrl-names = "default";
pinctrl-0 = <&spi0_pins>;
status = "okay";
spidbg@0x00 {
compatible = "spidbg";
spi-max-frequency = <100000000>;
reg = <0>;
};
};
~~~
插入驱动的时候通过compatible = "spidbg";就能匹配上驱动。
### 模块入口
基础信息:
~~~
//记录主设备号
int major = 252;
//字符驱动操作
static const struct file_operations spidbg_fops = {
.owner = THIS_MODULE,
.write = spidbg_write,
.read = spidbg_read,
.unlocked_ioctl = spidbg_ioctl,
.open = spidbg_open,
.release = spidbg_release,
.llseek = no_llseek,
};
//匹配的设备树信息
static const struct of_device_id spidbg_dt_ids[] = {
{ .compatible = "spidbg" },
{},
};
MODULE_DEVICE_TABLE(of, spidbg_dt_ids);
//spi驱动信息
static struct spi_driver spidbg_spi_driver = {
.driver = {
.name = "spidbg",
.of_match_table = of_match_ptr(spidbg_dt_ids),
},
.probe = spidbg_probe,
.remove = spidbg_remove,
};
~~~
模块出入口
~~~
//注册SPI设备
static int __init spidbg_driver_module_init(void)
{
int ret;
int major;
printk("spidbg_driver_module_init\r\n");
//注册字符驱动
major = register_chrdev(0, "spidbg", &spidbg_fops);
if (major < 0)
{
printk(KERN_ERR "register_chrdev fail\n");
return -EINVAL;
}
printk("register_chrdev success... spidbg major = %d.\n", major);
//注册类
spidbg_class = class_create(THIS_MODULE, "spidbg");
if (IS_ERR(spidbg_class)) {
unregister_chrdev(major, spidbg_spi_driver.driver.name);
return PTR_ERR(spidbg_class);
}
//注册驱动
ret = spi_register_driver(&spidbg_spi_driver);
if (ret < 0) {
class_destroy(spidbg_class);
unregister_chrdev(major, spidbg_spi_driver.driver.name);
}
return ret;
}
static void __exit spidbg_driver_module_exit(void)
{
printk("spidbg_driver_module_exit\r\n");
spi_unregister_driver(&spidbg_spi_driver);
class_destroy(spidbg_class);
unregister_chrdev(major, spidbg_spi_driver.driver.name);
}
module_init(spidbg_driver_module_init);
module_exit(spidbg_driver_module_exit);
MODULE_AUTHOR("zepan <zepanwucai@gmail.com>");
MODULE_DESCRIPTION("SPIDBG driver");
MODULE_LICENSE("GPL");
~~~
### 驱动插入移除
~~~
struct spidbg_data {
dev_t devt;
spinlock_t spi_lock;
struct spi_device *spi;
/* TX/RX buffers are NULL unless this device is open (users > 0) */
struct mutex buf_lock;
unsigned users;
u8 *tx_buffer;
u8 *rx_buffer;
u32 speed_hz;
};
#define bufsiz 4096
unsigned char tx_buf[bufsiz];
unsigned char rx_buf[bufsiz];
struct spidbg_data spidbg_dat;
struct class *spidbg_class;
/*
struct spi_device {
struct device dev;
struct spi_controller *controller;
struct spi_controller *master; /* compatibility layer */
u32 max_speed_hz;
u8 chip_select;
u8 bits_per_word;
u16 mode;
int irq;
void *controller_state;
void *controller_data;
char modalias[SPI_NAME_SIZE];
int cs_gpio; /* chip select gpio */
struct spi_statistics statistics;
};
*/
/*
#define SPI_CPHA 0x01 /* clock phase */
#define SPI_CPOL 0x02 /* clock polarity */
#define SPI_MODE_0 (0|0) /* (original MicroWire) */
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04 /* chipselect active high? */
#define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire */
#define SPI_3WIRE 0x10 /* SI/SO signals shared */
#define SPI_LOOP 0x20 /* loopback mode */
#define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */
#define SPI_READY 0x80 /* slave pulls low to pause */
#define SPI_TX_DUAL 0x100 /* transmit with 2 wires */
#define SPI_TX_QUAD 0x200 /* transmit with 4 wires */
#define SPI_RX_DUAL 0x400 /* receive with 2 wires */
#define SPI_RX_QUAD 0x800 /* receive with 4 wires */
*/
static int spidbg_probe(struct spi_device *spi)
{
struct spidbg_data *spidbg;
int status;
unsigned long minor=0;
//申请驱动数据
spidbg = kzalloc(sizeof(*spidbg), GFP_KERNEL);
if (!spidbg)
return -ENOMEM;
//初始化驱动数据
spidbg->spi = spi;
spidbg->tx_buffer = tx_buf;
spidbg->rx_buffer = rx_buf;
spin_lock_init(&spidbg->spi_lock);
mutex_init(&spidbg->buf_lock);
{
struct device *dev;
spidbg->devt = MKDEV(major, minor);
dev = device_create(spidbg_class, &spi->dev, spidbg->devt,
spidbg, "spidbg%d.%d",
spi->master->bus_num, spi->chip_select);
status = PTR_ERR_OR_ZERO(dev);
}
spidbg->speed_hz = spi->max_speed_hz;
if (status == 0)
spi_set_drvdata(spi, spidbg); //将数据寄存于spi设备中
else
kfree(spidbg);
return status;
}
static int spidbg_remove(struct spi_device *spi)
{
struct spidbg_data *spidbg = spi_get_drvdata(spi);
/* make sure ops on existing fds can abort cleanly */
spin_lock_irq(&spidbg->spi_lock);
spidbg->spi = NULL;
spin_unlock_irq(&spidbg->spi_lock);
/* prevent new opens */
if (spidbg->users == 0)
kfree(spidbg);
return 0;
}
~~~
### 字符设备操作之打开,关闭
~~~
static int spidbg_open(struct inode *inode, struct file *filp)
{
int status = -ENXIO;
struct spidbg_data *spidbg = &spidbg_dat;
if(spidbg->devt == inode->i_rdev) //判断设备号是否相符
status = 0;
if (status) {
pr_debug("spidbg: nothing for minor %d\n", iminor(inode));
goto err_find_dev;
}
spidbg->users++;
filp->private_data = spidbg; //关联数据
nonseekable_open(inode, filp); //不支持seek
return 0;
err_find_dev:
return status;
}
static int spidbg_release(struct inode *inode, struct file *filp)
{
struct spidbg_data *spidbg;
spidbg = filp->private_data;
filp->private_data = NULL;
//如果没人在使用了,并且已经remove了,释放资源
spidbg->users--;
if (!spidbg->users) {
int dofree;
spin_lock_irq(&spidbg->spi_lock);
dofree = (spidbg->spi == NULL);
spin_unlock_irq(&spidbg->spi_lock);
if (dofree)
kfree(spidbg);
}
return 0;
}
~~~
### 字符设备操作之读写
首先实现底层操作:
~~~
static ssize_t
spidbg_sync(struct spidbg_data *spidbg, struct spi_message *message)
{
int status;
struct spi_device *spi;
spin_lock_irq(&spidbg->spi_lock);
spi = spidbg->spi;
spin_unlock_irq(&spidbg->spi_lock);
if (spi == NULL)
status = -ESHUTDOWN;
else
status = spi_sync(spi, message); //spi驱动核心接口
if (status == 0)
status = message->actual_length;
return status;
}
static inline ssize_t
spidbg_sync_write(struct spidbg_data *spidbg, size_t len)
{
struct spi_transfer t = {
.tx_buf = spidbg->tx_buffer,
.len = len,
.speed_hz = spidbg->speed_hz,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spidbg_sync(spidbg, &m);
}
static inline ssize_t
spidbg_sync_read(struct spidbg_data *spidbg, size_t len)
{
struct spi_transfer t = {
.rx_buf = spidbg->rx_buffer,
.len = len,
.speed_hz = spidbg->speed_hz,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spidbg_sync(spidbg, &m);
}
~~~
读写函数:
~~~
/* Read-only message with current device setup */
static ssize_t
spidbg_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct spidbg_data *spidbg;
ssize_t n = 0;
/* chipselect only toggles at start or end of operation */
if (count > bufsiz)
return -EMSGSIZE;
spidbg = filp->private_data;
mutex_lock(&spidbg->buf_lock);
n = spidbg_sync_read(spidbg, count);
if (n > 0) {
unsigned long missing;
missing = copy_to_user(buf, spidbg->rx_buffer, n);
if (missing == n)
n = -EFAULT;
else
n = n - missing;
}
mutex_unlock(&spidbg->buf_lock);
return n;
}
/* Write-only message with current device setup */
static ssize_t
spidev_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
struct spidbg_data *spidbg;
ssize_t n = 0;
unsigned long missing;
/* chipselect only toggles at start or end of operation */
if (count > bufsiz)
return -EMSGSIZE;
spidbg = filp->private_data;
mutex_lock(&spidbg->buf_lock);
missing = copy_from_user(spidbg->tx_buffer, buf, count);
if (missing == 0)
n = spidbg_sync_write(spidbg, count);
else
n = -EFAULT;
mutex_unlock(&spidbg->buf_lock);
return status;
}
~~~
### IOCTL
~~~
static long
spidbg_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int retval = 0;
struct spidbg_data *spidbg;
struct spi_device *spi;
u32 tmp;
unsigned n_ioc;
struct spi_ioc_transfer *ioc;
//检查命令magic是否是spi命令
if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)
return -ENOTTY;
/* guard against device removal before, or while,
* we issue this ioctl.
*/
spidbg = filp->private_data;
spin_lock_irq(&spidbg->spi_lock);
spi = spi_dev_get(spidbg->spi); //获取spi设备
spin_unlock_irq(&spidbg->spi_lock);
if (spi == NULL)
return -ESHUTDOWN;
/* use the buffer lock here for triple duty:
* - prevent I/O (from us) so calling spi_setup() is safe;
* - prevent concurrent SPI_IOC_WR_* from morphing
* data fields while SPI_IOC_RD_* reads them;
* - SPI_IOC_MESSAGE needs the buffer locked "normally".
*/
mutex_lock(&spidbg->buf_lock);
switch (cmd) {
//IOCTL读请求
case SPI_IOC_RD_MODE:
retval = put_user(spi->mode & SPI_MODE_MASK, (__u8 __user *)arg);
break;
case SPI_IOC_RD_LSB_FIRST:
retval = put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0, (__u8 __user *)arg);
printk("%csb first\n", (spi->mode & SPI_LSB_FIRST) ? 'l' : 'm');
break;
case SPI_IOC_RD_BITS_PER_WORD:
retval = put_user(8, (__u8 __user *)arg); //v3s 固定只有8bit模式
break;
case SPI_IOC_RD_MAX_SPEED_HZ:
retval = put_user(spi->max_speed_hz, (__u32 __user *)arg);
break;
//IOCTL 写请求
case SPI_IOC_WR_MODE:
case SPI_IOC_WR_LSB_FIRST:
retval = get_user(tmp, (u8 __user *)arg);
if (retval == 0) {
u32 save = spi->mode;
if(cmd == SPI_IOC_WR_MODE)
{
tmp = tmp & SPI_MODE_MASK;
tmp |= spi->mode & ~SPI_MODE_MASK;
}
else
tmp |= spi->mode & ~SPI_LSB_FIRST;
spi->mode = (u16)tmp;
retval = spi_setup(spi);
if (retval < 0)
spi->mode = save;
else
printk(&spi->dev, "spi mode %x\n", tmp);
}
break;
case SPI_IOC_WR_BITS_PER_WORD:
case SPI_IOC_WR_MAX_SPEED_HZ:
printk("not implement\n");
break;
default:
//用户层的一次读写请求
//struct spi_ioc_transfer mesg[4];
//status = ioctl(fd, SPI_IOC_MESSAGE(4), mesg);
//下面的ioc是拷贝到内核空间的ioc消息内容
ioc = spidbg_get_ioc_message(cmd, (struct spi_ioc_transfer __user *)arg, &n_ioc);
if (IS_ERR(ioc)) {
retval = PTR_ERR(ioc);
break;
}
if (!ioc)
break; /* n_ioc is also 0 */
//翻译成spi_message,执行 */
retval = spidbg_message(spidbg, ioc, n_ioc);
kfree(ioc);
break;
}
mutex_unlock(&spidbg->buf_lock);
spi_dev_put(spi); //释放spi设备
return retval;
}
~~~
ioc message的处理
~~~
/*
struct spi_ioc_transfer {
__u64 tx_buf;
__u64 rx_buf;
__u32 len;
__u32 speed_hz;
__u16 delay_usecs;
__u8 bits_per_word;
__u8 cs_change;
__u8 tx_nbits;
__u8 rx_nbits;
__u16 pad;
};
*/
static struct spi_ioc_transfer *
spidbg_get_ioc_message(unsigned int cmd, struct spi_ioc_transfer __user *u_ioc,
unsigned *n_ioc)
{
u32 tmp;
/* Check type, command number and direction */
if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC
|| _IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0))
|| _IOC_DIR(cmd) != _IOC_WRITE)
return ERR_PTR(-ENOTTY);
tmp = _IOC_SIZE(cmd); //最大16KB
if ((tmp % sizeof(struct spi_ioc_transfer)) != 0)
return ERR_PTR(-EINVAL);
*n_ioc = tmp / sizeof(struct spi_ioc_transfer);
if (*n_ioc == 0)
return NULL;
/* copy into scratch area */
return memdup_user(u_ioc, tmp); //这里动态申请了内存
}
//执行ioc命令
static int spidbg_message(struct spidbg_data *spidbg, struct spi_ioc_transfer *u_xfers, unsigned n_xfers)
{
struct spi_message msg;
struct spi_transfer *k_xfers;
struct spi_transfer *k_tmp;
struct spi_ioc_transfer *u_tmp;
unsigned n, total, tx_total, rx_total;
u8 *tx_buf, *rx_buf;
int status = -EFAULT;
spi_message_init(&msg);
k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL);
if (k_xfers == NULL)
return -ENOMEM;
//构建spi_message
tx_buf = spidbg->tx_buffer;
rx_buf = spidbg->rx_buffer;
total = 0;
tx_total = 0;
rx_total = 0;
for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
n;
n--, k_tmp++, u_tmp++) {
k_tmp->len = u_tmp->len;
total += k_tmp->len;
if (total > INT_MAX || k_tmp->len > INT_MAX) {
status = -EMSGSIZE;
goto done;
}
if (u_tmp->rx_buf) {
/* this transfer needs space in RX bounce buffer */
rx_total += k_tmp->len;
if (rx_total > bufsiz) {
status = -EMSGSIZE;
goto done;
}
k_tmp->rx_buf = rx_buf;
rx_buf += k_tmp->len;
}
if (u_tmp->tx_buf) {
/* this transfer needs space in TX bounce buffer */
tx_total += k_tmp->len;
if (tx_total > bufsiz) {
status = -EMSGSIZE;
goto done;
}
k_tmp->tx_buf = tx_buf; //拷贝将发送的数据
if (copy_from_user(tx_buf, (const u8 __user *)
(uintptr_t) u_tmp->tx_buf,
u_tmp->len))
goto done;
tx_buf += k_tmp->len;
}
//依次拷贝字段
k_tmp->cs_change = !!u_tmp->cs_change;
k_tmp->tx_nbits = u_tmp->tx_nbits;
k_tmp->rx_nbits = u_tmp->rx_nbits;
k_tmp->bits_per_word = u_tmp->bits_per_word;
k_tmp->delay_usecs = u_tmp->delay_usecs;
k_tmp->speed_hz = u_tmp->speed_hz;
if (!k_tmp->speed_hz)
k_tmp->speed_hz = spidbg->speed_hz;
spi_message_add_tail(k_tmp, &msg);
}
status = spidbg_sync(spidbg, &msg);
if (status < 0)
goto done;
/* copy any rx data out of bounce buffer */
rx_buf = spidbg->rx_buffer;
for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) {
if (u_tmp->rx_buf) {
if (copy_to_user((u8 __user *) (uintptr_t) u_tmp->rx_buf, rx_buf, u_tmp->len)) {
status = -EFAULT;
goto done;
}
rx_buf += u_tmp->len;
}
}
status = total;
done:
kfree(k_xfers);
return status;
}
~~~
### 需要的头文件
~~~
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>
#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>
#include <linux/uaccess.h>
~~~
### 单独编译模块
make M=drivers/staging/spidbg modules
- 前言
- 荔枝派TODO任务领取
- linux使用小贴士
- 入门篇
- 板卡介绍
- 开箱指南
- 烧录启动系统
- 联网方法
- 镜像使用
- 镜像说明
- buildroot系统使用
- debian系统使用
- 外设操作
- 外设操作概览
- 低速外设
- GPIO
- GPIO模拟低速接口
- UART
- PWM
- I2C
- SPI
- 高速接口
- SDIO
- USB
- EtherNet
- DVP CSI
- MIPI CSI
- 模拟外设
- CODEC
- LRADC
- 常见设备驱动
- USB摄像头
- USB 3G/4G 网卡
- 舵机
- 开发篇
- UBOOT适配
- UBOOT编译
- UBOOT配置
- UBOOT配置屏幕分辨率
- UBOOT配置SPI启动
- Linux内核开发
- Linux内核编译
- BSP Linux内核编译.md
- Linux内核选项
- 外设驱动与设备树
- RTL8723BS驱动
- 根文件系统定制
- buildroot定制系统
- buildroot添加软件包
- openwrt定制系统
- emdebian定制系统
- camdriod开发
- camdriod编译
- 主线Uboot引导Camdriod
- 系统镜像打包
- XBOOT适配
- 荔枝运行XBOOT
- 应用篇
- 游戏机-基于EmulationStation
- 游戏机-gnuboy
- 语音识别-科大讯飞云
- GUI-QT5
- 语音识别-离线关键词识别
- 路由器-Lichee Zero
- 投稿文章
- 荔枝派Zero开箱指南
- Zero i2c oled使用指南
- zero SPI LCD使用指南
- Zero u-boot编译和使用指南
- TF WiFi使用方法
- Zero Ethernet使用指南
- Zero 移植Qt5.4.1
- ZeroSpiNorFlash启动系统制作指南
- Visio-uboot-sunxi流程
- lichee 编译踩坑记录(ilichee ZERO)
- lichee_zero_外设GPIO接口
- TF WIFI 小白编
- 从零开始LicheePi Zero的开发
- 认识Zero的硬件
- 搭建Zero的开发环境
- 主线Uboot
- 主线kernel
- BSP kernel
- BSP内核启动
- bsp内核的摄像头使用
- BSP内核中的保留内存
- uboot启动BSP内核常见错误
- BSP内核 FBTFT移植
- BSP内核启动错误及警告解决
- buildroot 根文件系统
- emdebian 根文件系统
- SPI Flash 系统编译
- sunxi-fel增加对16M 以上flash的支持
- overlayfs的使用
- jffs2系统挂载不上的常见原因
- JFFS2 文件系统简介
- uboot对spi flash的识别
- bsp内核的SPI flash启动
- Docker开发环境
- Docker 命令速查
- 基础ubuntu系统配置
- docker离线镜像
- Zero系统烧录
- dd镜像烧录
- 分区镜像烧录
- SPI Flash系统烧录
- 一键镜像烧录
- Zero外设把玩
- I2C操作
- PWM输出
- CODEC的使用
- 以太网使用指南
- GPIO操作
- 文件IO方式
- C语言接口(mmap)
- Python操作GPIO
- pinctrl-sunxi介绍
- UART操作
- 点屏
- 点屏之RGB屏
- 点屏之SPI屏 ili9341
- 点屏之SPI OLED
- 点屏之I2C OLED
- 点屏之SPI屏 ili9488
- 点屏之MCU屏
- 点屏之触摸屏驱动
- 点屏之simple-framebuffer
- 点屏之屏幕时序
- 时钟控制器CCM
- 摄像头
- BSP DVP摄像头
- BSP MIPI 摄像头
- 主线DVP摄像头
- 主线 MIPI摄像头
- SPI 操作
- 应用层开发
- 开机自启动
- Segment Fault调试
- Zero通过OTG共享PC网络
- USB摄像头使用
- 基于QT的GUI开发
- 移植tslib
- 移植QT5.9.1
- 移植QT4.8.7
- QtCreator使用
- Qt5.x移植到Qt4.8
- Qt字体相关
- Qt移植总结
- Qt裁剪
- Qt去除鼠标指针显示
- zero_imager使用
- 驱动开发
- 设备树简介
- GPU/DRM 显示驱动
- sys下设备树查看
- atmel触摸屏驱动分析
- atmel触摸屏中断改轮询
- uboot下gpio操作
- helloworld驱动编译演示
- FBTFT分析
- 内核模块静态加载的顺序
- SPI驱动分析
- SPI 驱动编写
- Uboot开发
- 开机logo
- 看门狗的使用
- 关于系统reboot
- 内核printk等级设置