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 {
pinctrl@fffff200 {
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: spi@f8008000 {
#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: spi@f8008000 {
dmas = <0>, <0>; /* 禁用DMA */
pinctrl-0 = <&pinctrl_spi1 &pinctrl_spi1_cs3>; /* 增加cs腳的復用 */

spidev@3 {
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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM