# 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等級設置