Linux裝置驅動探究第1天----spi驅動(1)
本文允許轉載,請註明出處:http://blog.csdn.net/fulinus
Linux核心程式碼實在太大了,一個小小的模組也會讓你手足無措,今天下午決心要把spi驅動好好看看。
首先分析spidev.c檔案,這個檔案中定義struct file_operations結構中的成員。成員有spidev_write、spidev_read和spidev_ioctl,前兩者實現半雙工通訊,後者實現全雙工通訊。當然還有open和release等相關的成員,先忽略吧。
spidev_write -------->spidev_sync
spidev_write -------->spidev_sync
spidev_ioctl ------> spidev_message------->spidev_sync
詳細文件見:點選開啟連結
還有填充struct spi_drive資料結構體成員的函式,有spidev_probe,spidev_remove函式。以及裝置驅動的初始化和退出函式:spidev_init和spidev_exit
程式碼分析見:點選開啟連結
引用:
#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/spi/spi.h>
#include <linux/spi/spidev.h>
#include <asm/uaccess.h>
#define SPIDEV_MAJOR 153 //spidev主裝置號
#define N_SPI_MINORS 32 /* ... up to 256 */
static DECLARE_BITMAP(minors, N_SPI_MINORS); //宣告次裝置點陣圖
#define SPI_MODE_MASK (SPI_CPHA|SPI_CPOL|SPI_CS_HIGH|SPI_LSB_FIRST|SPI_3WIRE|SPI_LOOP|SPI_NO_CS|SPI_READY)
struct spidev_data {
dev_t devt; //裝置號
spinlock_t spi_lock; //自旋鎖
struct spi_device *spi; //spi裝置結構體
struct list_head device_entry;
struct mutex buf_lock; //互斥鎖
unsigned users; //使用者計數
u8 *buffer; //緩衝區
};
static LIST_HEAD(device_list); //宣告spi裝置連結串列
static DEFINE_MUTEX(device_list_lock); //定義互斥鎖
static unsigned bufsiz = 4096; //最大傳輸緩衝區大小
module_param(bufsiz, uint, S_IRUGO);
MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message");
static void spidev_complete(void *arg)
{
complete(arg); //呼叫complete
}
static ssize_t spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{
DECLARE_COMPLETION_ONSTACK(done);
int status;
message->complete = spidev_complete; //設定spi訊息的complete方法 回撥函式
message->context = &done;
spin_lock_irq(&spidev->spi_lock);
if (spidev->spi == NULL) //判斷是否有指定對應的spi裝置
status = -ESHUTDOWN;
else
status = spi_async(spidev->spi, message); //spi非同步同步
spin_unlock_irq(&spidev->spi_lock);
if (status == 0) {
wait_for_completion(&done); //等待傳輸完成
status = message->status; //獲取spi訊息傳輸事務狀態
if (status == 0)
status = message->actual_length; //status等於傳輸的實際長度
}
return status; //返回實際傳輸長度
}
static inline ssize_t spidev_sync_write(struct spidev_data *spidev, size_t len)
{
struct spi_transfer t = {
.tx_buf = spidev->buffer, //傳送緩衝區
.len = len, //傳送資料長度
};
struct spi_message m;
spi_message_init(&m); //初始化spi訊息(初始化spi傳遞事務佇列)
spi_message_add_tail(&t, &m); //新增spr傳遞到該佇列
return spidev_sync(spidev, &m); //同步讀寫
}
static inline ssize_t spidev_sync_read(struct spidev_data *spidev, size_t len)
{
struct spi_transfer t = {
.rx_buf = spidev->buffer, //接收緩衝區
.len = len, //接收資料長度
};
struct spi_message m;
spi_message_init(&m); //初始化spi訊息(初始化spi傳遞事務佇列)
spi_message_add_tail(&t, &m); //新增spr傳遞到該佇列
return spidev_sync(spidev, &m); //同步讀寫
}
static ssize_t spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct spidev_data *spidev;
ssize_t status = 0;
if (count > bufsiz) //傳輸資料大於緩衝區容量
return -EMSGSIZE;
spidev = filp->private_data; //從檔案私有資料指標獲取spidev_data
mutex_lock(&spidev->buf_lock); //上互斥鎖
status = spidev_sync_read(spidev, count); //同步讀,返回傳輸資料長度
if (status > 0) {
unsigned long missing; //丟失的資料個數
missing = copy_to_user(buf, spidev->buffer, status); //核心空間複製到使用者空間
if (missing == status) //丟失的資料個數等於要傳輸的資料個數
status = -EFAULT;
else
status = status - missing; //傳輸成功的資料個數
}
mutex_unlock(&spidev->buf_lock);//解互斥鎖
return status; //返回讀取成功的資料個數
}
static ssize_t spidev_write(struct file *filp, const char __user *buf,size_t count, loff_t *f_pos)
{
struct spidev_data *spidev;
ssize_t status = 0;
unsigned long missing;
if (count > bufsiz) //傳輸資料大於緩衝區容量
return -EMSGSIZE;
spidev = filp->private_data; //從檔案私有資料指標獲取spidev_data
mutex_lock(&spidev->buf_lock); //上互斥鎖
missing = copy_from_user(spidev->buffer, buf, count); //使用者空間複製到核心空間
if (missing == 0) { //傳輸失敗個數為0
status = spidev_sync_write(spidev, count); //同步寫,返回傳輸資料長度
}
else
status = -EFAULT;
mutex_unlock(&spidev->buf_lock);//解互斥鎖
return status; //返回寫資料的實際個數
}
static int spidev_message(struct spidev_data *spidev,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;
u8 *buf;
int status = -EFAULT;
spi_message_init(&msg); //初始化spi訊息(初始化spi傳遞事務佇列)
k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL); //分配spi傳輸指標記憶體
if (k_xfers == NULL)
return -ENOMEM;
buf = spidev->buffer; //獲取spidev_data的緩衝區
total = 0;
//n=xfers為spi_ioc_transfer個數,u_tmp = u_xfers為要處理的spi_ioc_transfer指標
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 > bufsiz) { //資訊量超過bufsiz緩衝區最大容量
status = -EMSGSIZE;
goto done;
}
if (u_tmp->rx_buf) { //接收緩衝區指標不為空
k_tmp->rx_buf = buf; //緩衝區指向buf
if (!access_ok(VERIFY_WRITE, (u8 __user *)(uintptr_t) u_tmp->rx_buf,u_tmp->len))
goto done;
}
if (u_tmp->tx_buf) { //傳送緩衝區指標不為空
k_tmp->tx_buf = buf; //緩衝區指標指向buf
if (copy_from_user(buf, (const u8 __user *)(uintptr_t) u_tmp->tx_buf,u_tmp->len)) //使用者空間複製資料到buf
goto done;
}
buf += k_tmp->len; //緩衝區指標移動一個傳輸資訊的長度
k_tmp->cs_change = !!u_tmp->cs_change; //設定cs_change
k_tmp->bits_per_word = u_tmp->bits_per_word; //設定bits_per_word 一個字多少位
k_tmp->delay_usecs = u_tmp->delay_usecs; //設定delay_usecs 毫秒級延時
k_tmp->speed_hz = u_tmp->speed_hz; //設定speed_hz 速率
#ifdef VERBOSE
dev_dbg(&spidev->spi->dev," xfer len %zd %s%s%s%dbits %u usec %uHz\n",
u_tmp->len,u_tmp->rx_buf ? "rx " : "",u_tmp->tx_buf ? "tx " : "",u_tmp->cs_change ? "cs " : "",
u_tmp->bits_per_word ? : spidev->spi->bits_per_word,u_tmp->delay_usecs,u_tmp->speed_hz ? : spidev->spi->max_speed_hz);
#endif
spi_message_add_tail(k_tmp, &msg); //新增spr傳遞到該佇列
}
//for迴圈的作用是將spi_ioc_transfer批量轉換為spi傳遞結構體spi_transfer,然後新增進spi傳遞事務佇列
status = spidev_sync(spidev, &msg); //同步讀寫
if (status < 0)
goto done;
buf = spidev->buffer; //獲取spidev_data緩衝區指標
for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) { //批量從核心空間複製spi_ioc_transfer到使用者空間
if (u_tmp->rx_buf) { //判斷是否存在接收緩衝區
if (__copy_to_user((u8 __user *)(uintptr_t) u_tmp->rx_buf, buf,u_tmp->len)) {
status = -EFAULT;
goto done;
}
}
buf += u_tmp->len; //buf指標位置調整指向下一個spi_ioc_transfer
}
status = total; //status等於實際傳輸的資料長度
done:
kfree(k_xfers); //釋放k_xfers
return status; //返回實際傳輸的資料長度
}
static long spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int err = 0;
int retval = 0;
struct spidev_data *spidev;
struct spi_device *spi;
u32 tmp;
unsigned n_ioc;
struct spi_ioc_transfer *ioc;
if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC) //判斷控制命令的型別
return -ENOTTY;
if (_IOC_DIR(cmd) & _IOC_READ) //判斷控制命令的方向是否為讀read
err = !access_ok(VERIFY_WRITE,(void __user *)arg, _IOC_SIZE(cmd)); //判斷傳輸資料大小
if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE) //判斷控制命令的方向是否為寫write
err = !access_ok(VERIFY_READ,(void __user *)arg, _IOC_SIZE(cmd)); //判斷傳輸資料大小
if (err)
return -EFAULT;
spidev = filp->private_data; //從檔案私有資料中獲取spidev_data
spin_lock_irq(&spidev->spi_lock); //上自旋鎖
spi = spi_dev_get(spidev->spi); //獲取spi裝置
spin_unlock_irq(&spidev->spi_lock); //解自旋鎖
if (spi == NULL) //獲取spi裝置失敗
return -ESHUTDOWN; //則返回錯誤
mutex_lock(&spidev->buf_lock); //上互斥鎖
switch (cmd) {
case SPI_IOC_RD_MODE: //設定spi讀模式
retval = __put_user(spi->mode & SPI_MODE_MASK,(__u8 __user *)arg);
break;
case SPI_IOC_RD_LSB_FIRST: //設定spi讀最低有效位
retval = __put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0,(__u8 __user *)arg);
break;
case SPI_IOC_RD_BITS_PER_WORD: //設定spi讀每個字含多個個位
retval = __put_user(spi->bits_per_word, (__u8 __user *)arg);
break;
case SPI_IOC_RD_MAX_SPEED_HZ: //設定spi讀最大速率
retval = __put_user(spi->max_speed_hz, (__u32 __user *)arg);
break;
case SPI_IOC_WR_MODE: //設定spi寫模式
retval = __get_user(tmp, (u8 __user *)arg);
if (retval == 0) {
u8 save = spi->mode; //獲取spi裝置模式
if (tmp & ~SPI_MODE_MASK) {
retval = -EINVAL;
break;
}
tmp |= spi->mode & ~SPI_MODE_MASK;
spi->mode = (u8)tmp;
retval = spi_setup(spi); //配置spi裝置
if (retval < 0)
spi->mode = save;
else
dev_dbg(&spi->dev, "spi mode %02x\n", tmp);
}
break;
case SPI_IOC_WR_LSB_FIRST: //設定spi寫最低有效位
retval = __get_user(tmp, (__u8 __user *)arg);
if (retval == 0) {
u8 save = spi->mode; //獲取spi裝置模式
if (tmp)
spi->mode |= SPI_LSB_FIRST;
else
spi->mode &= ~SPI_LSB_FIRST;
retval = spi_setup(spi); //配置spi裝置
if (retval < 0)
spi->mode = save;
else
dev_dbg(&spi->dev, "%csb first\n",tmp ? 'l' : 'm');
}
break;
case SPI_IOC_WR_BITS_PER_WORD: //設定spi寫每個字含多個個位
retval = __get_user(tmp, (__u8 __user *)arg); //使用者空間獲取資料
if (retval == 0) {
u8 save = spi->bits_per_word; //獲取spi裝置 每個字含多少位
spi->bits_per_word = tmp; //更新新的spi裝置 每個字含多少位
retval = spi_setup(spi); //配置spi裝置
if (retval < 0) //配置失敗
spi->bits_per_word = save; //還原spi裝置 每個字含多少位
else
dev_dbg(&spi->dev, "%d bits per word\n", tmp);
}
break;
case SPI_IOC_WR_MAX_SPEED_HZ: //設定spi寫最大速率
retval = __get_user(tmp, (__u32 __user *)arg); //使用者空間獲取資料
if (retval == 0) {
u32 save = spi->max_speed_hz; //獲取spi裝置最大速率
spi->max_speed_hz = tmp; //更新新的spi裝置最大速率
retval = spi_setup(spi); //配置spi裝置
if (retval < 0) //配置失敗
spi->max_speed_hz = save; //還原spi裝置最大速率
else
dev_dbg(&spi->dev, "%d Hz (max)\n", tmp);
}
break;
default:
//命令必須為寫方向的命令,且傳輸資料必須是SPI_IOC_MESSAGE()修飾的命令
if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0))|| _IOC_DIR(cmd) != _IOC_WRITE) {
retval = -ENOTTY;
break;
}
tmp = _IOC_SIZE(cmd); //計算傳輸資料大小
if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) { //判斷是否為spi_ioc_transfer對齊
retval = -EINVAL;
break;
}
n_ioc = tmp / sizeof(struct spi_ioc_transfer); //計算出spi_ioc_transfer資料的個數
if (n_ioc == 0)
break;
ioc = kmalloc(tmp, GFP_KERNEL); //分配spi_ioc_transfer指標ioc記憶體
if (!ioc) {
retval = -ENOMEM;
break;
}
if (__copy_from_user(ioc, (void __user *)arg, tmp)) { //從使用者空間複製到核心空間
kfree(ioc); //複製失敗則釋放ioc記憶體
retval = -EFAULT;
break;
}
retval = spidev_message(spidev, ioc, n_ioc); //spidev訊息處理
kfree(ioc); //釋放ioc記憶體
break;
}
mutex_unlock(&spidev->buf_lock); //解互斥鎖
spi_dev_put(spi); //增加spi裝置的引用計數
return retval;
}
static int spidev_open(struct inode *inode, struct file *filp)
{
struct spidev_data *spidev;
int status = -ENXIO;
mutex_lock(&device_list_lock); //上互斥鎖
list_for_each_entry(spidev, &device_list, device_entry) { //遍歷device_list
if (spidev->devt == inode->i_rdev) { //判斷裝置號找到對應的裝置
status = 0; //設定狀態為0
break;
}
}
if (status == 0) { //找得到對應的裝置
if (!spidev->buffer) { //spidev_data緩衝區為空
spidev->buffer = kmalloc(bufsiz, GFP_KERNEL); //則分配記憶體
if (!spidev->buffer) { //還空
dev_dbg(&spidev->spi->dev, "open/ENOMEM\n"); //除錯了
status = -ENOMEM;
}
}
if (status == 0) { //找得到對應的裝置
spidev->users++; //spidev_data使用者計數++
filp->private_data = spidev; //spidev_data放在檔案的私有資料裡
nonseekable_open(inode, filp); //設定檔案的開啟模式(檔案讀寫指標不會跟隨讀寫操作移動)
}
}
else
pr_debug("spidev: nothing for minor %d\n", iminor(inode));
mutex_unlock(&device_list_lock); //接互斥鎖
return status;
}
static int spidev_release(struct inode *inode, struct file *filp)
{
struct spidev_data *spidev;
int status = 0;
mutex_lock(&device_list_lock);
spidev = filp->private_data; //獲取spidev_data
filp->private_data = NULL; //清除檔案的私有資料指標
spidev->users--; //使用者個數--
if (!spidev->users) { //如果使用者個數為0
int dofree;
kfree(spidev->buffer); //釋放spidev_data的緩衝區記憶體
spidev->buffer = NULL; //清除spidev_data緩衝區指標
spin_lock_irq(&spidev->spi_lock); //上自旋鎖
dofree = (spidev->spi == NULL); //判斷spi裝置是否與spidev_data解綁了
spin_unlock_irq(&spidev->spi_lock); //解自旋鎖
if (dofree) //沒有捆綁的spi裝置
kfree(spidev); //則是否spidev_data記憶體
}
mutex_unlock(&device_list_lock);
return status;
}
static const struct file_operations spidev_fops = { //檔案操作函式集
.owner = THIS_MODULE,
.write = spidev_write, //寫write
.read = spidev_read, //讀read
.unlocked_ioctl = spidev_ioctl, //控制ioctl
.open = spidev_open, //開啟open
.release = spidev_release, //釋放release
.llseek = no_llseek, //檔案指標移動 no_llseek表示沒有移動
};
static struct class *spidev_class;
static int __devinit spidev_probe(struct spi_device *spi)
{
struct spidev_data *spidev;
int status;
unsigned long minor;
spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); //分配spidev_data記憶體
if (!spidev)
return -ENOMEM;
spidev->spi = spi; //設定spidev_data->spi(spi裝置)
spin_lock_init(&spidev->spi_lock);
mutex_init(&spidev->buf_lock);
INIT_LIST_HEAD(&spidev->device_entry); //初始化spidev_data入口連結串列
mutex_lock(&device_list_lock);
minor = find_first_zero_bit(minors, N_SPI_MINORS); //查詢次裝置點陣圖分配次裝置號
if (minor < N_SPI_MINORS) {
struct device *dev;
spidev->devt = MKDEV(SPIDEV_MAJOR, minor); //計算出裝置號
//建立裝置/dev/spidev%d.%d(spidev匯流排號.片選號)
dev = device_create(spidev_class, &spi->dev, spidev->devt,spidev, "spidev%d.%d",spi->master->bus_num, spi->chip_select);
status = IS_ERR(dev) ? PTR_ERR(dev) : 0;
}
else {
dev_dbg(&spi->dev, "no minor number available!\n");
status = -ENODEV;
}
if (status == 0) { //分配裝置號成功
set_bit(minor, minors); //更新次裝置點陣圖
list_add(&spidev->device_entry, &device_list); //新增進裝置連結串列
}
mutex_unlock(&device_list_lock);
if (status == 0)
spi_set_drvdata(spi, spidev); //spi->dev->p->driver_data=spidev
else
kfree(spidev);
return status;
}
static int __devexit spidev_remove(struct spi_device *spi)
{
struct spidev_data *spidev = spi_get_drvdata(spi); //根據spi裝置獲取spidev_data
spin_lock_irq(&spidev->spi_lock); //上自旋鎖
spidev->spi = NULL; //清空spidev_data->spi指標
spi_set_drvdata(spi, NULL); //spi->dev->p->driver_data=NULL
spin_unlock_irq(&spidev->spi_lock); //解自旋鎖
mutex_lock(&device_list_lock); //上互斥鎖
list_del(&spidev->device_entry); //刪除spidev_data入口連結串列
device_destroy(spidev_class, spidev->devt); //銷燬/dev/spidev%d.%d
clear_bit(MINOR(spidev->devt), minors); //清除次裝置點陣圖對應位
if (spidev->users == 0) //使用者個數為0
kfree(spidev); //釋放spidev_data記憶體
mutex_unlock(&device_list_lock); //解互斥鎖
return 0;
}
static struct spi_driver spidev_spi_driver = { //spi裝置驅動
.driver = {
.name = "spidev",
.owner = THIS_MODULE,
},
.probe = spidev_probe, //spidev的probe方法(當註冊了modalias域為"spidev"的spi裝置或板級裝置,則會呼叫probe方法)
.remove = __devexit_p(spidev_remove), //spidev的remove方法
};
static int __init spidev_init(void) //spidev介面初始化
{
int status;
BUILD_BUG_ON(N_SPI_MINORS > 256);
//註冊字元裝置,主裝置號SPIDEV_MAJOR=153,捆綁的裝置操作函式集為spidev_fops
status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
if (status < 0)
return status;
spidev_class = class_create(THIS_MODULE, "spidev"); //建立裝置類spidev_class
if (IS_ERR(spidev_class)) {
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
return PTR_ERR(spidev_class);
}
status = spi_register_driver(&spidev_spi_driver); //註冊spi裝置驅動spidev_spi_driver
if (status < 0) {
class_destroy(spidev_class);
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
}
return status;
}
module_init(spidev_init); //宣告初始化入口
static void __exit spidev_exit(void) //spidev介面銷燬
{
spi_unregister_driver(&spidev_spi_driver); //登出spi裝置驅動spidev_spi_driver
class_destroy(spidev_class); //登出裝置類spidev_class
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); //登出字元裝置
}
module_exit(spidev_exit); //宣告初始化出口
MODULE_AUTHOR("Andrea Paterniani, <a.paterniani@swapp-eng.it>");
MODULE_DESCRIPTION("User mode SPI device interface");
MODULE_LICENSE("GPL");
MODULE_ALIAS("spi:spidev");
這裡整理下ioctl的命令:
SPI_IOC_RD_MODE //讀 模式
SPI_IOC_RD_LSB_FIRST //讀 LSB
SPI_IOC_RD_BITS_PER_WORD //讀 每字多少位
SPI_IOC_RD_MAX_SPEED_HZ //讀 最大速率
SPI_IOC_WR_MODE //寫 模式
SPI_IOC_WR_LSB_FIRST //寫 LSB
SPI_IOC_WR_BITS_PER_WORD //寫 每字多少位
SPI_IOC_WR_MAX_SPEED_HZ //寫 最大速率
SPI_IOC_MESSAGE(n) //傳輸n個資料包
相關文章
- Linux裝置驅動之字元裝置驅動Linux字元
- 《Linux裝置驅動開發詳解(第2版)》——第1章Linux裝置驅動概述及開發環境構建1.1裝置驅動的作用Linux開發環境
- Linux裝置驅動程式學習----1.裝置驅動程式簡介Linux
- 【Linux SPI】RFID RC522 裝置驅動Linux
- 深入淺出:Linux裝置驅動之字元裝置驅動Linux字元
- 乾坤合一:Linux裝置驅動之塊裝置驅動Linux
- 字元裝置驅動 —— 字元裝置驅動框架字元框架
- 【linux】驅動-7-平臺裝置驅動Linux
- 蛻變成蝶:Linux裝置驅動之字元裝置驅動Linux字元
- 蛻變成蝶~Linux裝置驅動之字元裝置驅動Linux字元
- Linux塊裝置驅動Linux
- 【linux】驅動-6-匯流排-裝置-驅動Linux
- SPI驅動示例
- 框架-SPI四種模式+通用裝置驅動實現框架模式
- 乾坤合一:Linux裝置驅動之USB主機和裝置驅動Linux
- Linux裝置驅動程式 (轉)Linux
- SPI驅動框架一框架
- 驅動Driver-MISC雜項驅動裝置
- Linux驅動開發筆記(四):裝置驅動介紹、熟悉雜項裝置驅動和ubuntu開發雜項裝置DemoLinux筆記Ubuntu
- linux 裝置驅動基本概念Linux
- Linux驅動實踐:如何編寫【 GPIO 】裝置的驅動程式?Linux
- LED字元裝置驅動字元
- Linux驅動之I2C匯流排裝置以及驅動Linux
- Linux下的硬體驅動——USB裝置(上)(驅動配置部分)(轉)Linux
- 在Linux中,什麼是裝置驅動程式?如何安裝和解除安裝裝置驅動程式?Linux
- linux裝置驅動編寫入門Linux
- linux裝置驅動編寫基礎Linux
- LINUX下的裝置驅動程式 (轉)Linux
- 嵌入式Linux驅動筆記(十三)------spi裝置之RFID-rc522驅動Linux筆記
- Linux晶片驅動之SPI ControllerLinux晶片Controller
- 如何編寫一個簡單的Linux驅動(三)——完善裝置驅動Linux
- Linux的input輸入子系統:裝置驅動之按鍵驅動Linux
- Linux下的硬體驅動——USB裝置(下)(驅動開發部分)(轉)Linux
- 字元驅動裝置踩坑字元
- platform 裝置驅動實驗Platform
- 【linux】驅動-9-裝置樹外掛Linux
- linux驅動之獲取裝置樹資訊Linux
- linux裝置驅動中的併發控制Linux