imx6ull中GPIO涉及寄存器
1:CCM寄存器

00 :該 GPIO 模塊全程被關閉01 :該 GPIO 模塊在 CPU run mode 情況下是使能的;在 WAIT 或 STOP模式下,關閉10 :保留11 :該 GPIO 模塊全程使能
2:引腳功能寄存器
3:GPIO模塊內部
3)GPIOx_PSR:讀取引腳的電平,每位對應一個引腳,1-高電平,0-低電平
4:總結
1)讀GPIO
設置 CCM_CCGRx 寄存器中某位使能對應的 GPIO 模塊 // 默認是使能 的,設置 IOMUX 來選擇引腳用于 GPIO設置 GPIOx_GDIR 中某位為 0 ,把該引腳設置為輸入功能讀 GPIOx_DR 或 GPIOx_PSR 得到某位的值(讀 GPIOx_DR 返回的是 GPIOx_PSR 的值)
2)寫GPIO
設置 CCM_CCGRx 寄存器中某位使能對應的 GPIO 模塊 // 默認是使能的,設置 IOMUX 來選擇引腳用于 GPIO設置 GPIOx_GDIR 中某位為 1 ,把該引腳設置為輸出功能寫 GPIOx_DR 某位的值
2:代碼編寫
2.1:字符設備驅動程序框架
寫驅動程序的套路:
- 確定主設備號,也可以讓內核分配
- 定義自己的 file_operations 結構體
- 實現對應的 drv_open/drv_read/drv_write 等函數,填入 file_operations 結構體
- 把 file_operations 結構體告訴內核:register_chrdev
- 誰來注冊驅動程序啊?得有一個入口函數:安裝驅動程序時,就會去調用這個入口函數
- 有入口函數就應該有出口函數:卸載驅動程序時,出口函數調用unregister_chrdev
- 其他完善:提供設備信息,自動創建設備節點:class_create,device_create
驅動怎么操作硬件?
????????? 通過 ioremap 映射寄存器的物理地址得到虛擬地址,讀寫虛擬地址。
驅動怎么和 APP 傳輸數據?
????????? 通過 copy_to_user、copy_from_user 這 2 個函數
2.2:代碼邏輯圖
2.2:實現功能
先編寫驅動程序:? 實現 led_open 函數,在里面初始化 LED 引腳。? 實現 led_write 函數,在里面根據 APP 傳來的值控制 LED 。? 再編寫測試程序。
2.2.1:確定主設備號。
? ? ? ? /* 1. 確定主設備號*/
static int major = 0; ? ? ??
? ? ? ? //后續在入口函數中,使用下列代碼:注冊函數進行分配
major = register_chrdev(0, "hello", &hello_drv);
2.2.2:定義自己的?file_operations?結構體
static struct file_operations led_fops = {
? ? .owner ? ? ?= THIS_MODULE,
? ? .write ? ? ?= led_write,
? ? .open ? ? ? = led_open,
};
2.2.3:實現對應的?drv_open/drv_read/drv_write?等函數,??
注:我們現在編寫的函數屬于驅動函數,那么我們在串口掉用這些函數時,
? ? ? ? 串口那邊,我們屬于APP
? ? ? ? 而這些驅動屬于 “內核”
? ? ? ? 我們串口調用read函數 ?len = read(fd, buf, 1024);時,我們是需要把kernel_buf的數據讀到buf中。所以在這read代碼中,我們使用 ?err = copy_to_user(buf, kernel_buf, MIN(1024, size));這行代碼,將kernel_buf寫入到buf中。
? ? ? ? 我們串口調用write函數 write(fd, argv[2], len);時,是把argv[2]的數據寫入到kernel_buf中。所以在這write代碼中,我們使用 ?err = copy_from_user(kernel_buf, buf, MIN(1024, size));這行代碼,將buf也就是argv[2]寫入到kernel_buf中。
注2:驅動怎么操作硬件?
????????? 通過 ioremap 映射寄存器的物理地址得到虛擬地址,讀寫虛擬地址。
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
static volatile unsigned int *GPIO5_GDIR;
static volatile unsigned int *GPIO5_DR;
????????這是我們需要操作的三個寄存器,IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3在open函數中,將PIN配置為GPIO模式,GPIO5_GDIR在open函數中被配置為輸出模式,GPIO5_DR根據用戶傳輸的數據來點亮或者熄滅LED。
????????因為驅動不能直接訪問硬件地址,所以我們在后續入口函數中,會使用ioremap函數將物理地址映射至三個定義中,這樣后續對三個定義的操作就類似于直接對物理地址進行操作。
/* registers */
// IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 地址:0x02290000 + 0x14
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
// GPIO5_GDIR 地址:0x020AC004
static volatile unsigned int *GPIO5_GDIR;
//GPIO5_DR 地址:0x020AC000
static volatile unsigned int *GPIO5_DR;
static ssize_t led_write(struct file *filp, const char __user *buf,
? ? ? ? ? ? ?size_t count, loff_t *ppos)
{
? ? char val;
? ? int ret;? ?
? ? /* copy_from_user : get data from app */
? ? ret = copy_from_user(&val, buf, 1);
? ? /* to set gpio register: out 1/0 */
? ? if (val)
? ? {
? ? ? ? /* set gpio to let led on */
? ? ? ? *GPIO5_DR &= ~(1<<3);
? ? }
? ? else
? ? {
? ? ? ? /* set gpio to let led off */
? ? ? ? *GPIO5_DR |= (1<<3);
? ? }
? ? return 1;
}
static int led_open(struct inode *inode, struct file *filp)
{
? ? /* enable gpio5
? ? ?* configure gpio5_io3 as gpio
? ? ?* configure gpio5_io3 as output
? ? ?*/
? ? *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;
? ? *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x5;
? ? *GPIO5_GDIR |= (1<<3);
? ? return 0;
}
2.2.4:把 file_operations 結構體告訴內核:register_chrdev
2.2.5. 誰來注冊驅動程序啊?得有一個入口函數:?
?????????3.4和3.5可同時在入口函數中實現,安裝驅動程序時,系統去調用這個入口函數,這是直接在入口函數中直接將結構體告訴內核.
????????并且在這里我們還需要將物理地址進行映射,這樣后續open和write可以直接對硬件進行操作
/* 入口函數 */
static int __init led_init(void)
{
? ? printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
? ?
? ? major = register_chrdev(0, "100ask_led", &led_fops);
? ? /* ioremap */
? ? // IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 地址:0x02290000 + 0x14
? ? IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14, 4);
? ?
? ? // GPIO5_GDIR 地址:0x020AC004
? ? GPIO5_GDIR = ioremap(0x020AC004, 4);
? ?
? ? //GPIO5_DR 地址:0x020AC000
? ? GPIO5_DR ?= ioremap(0x020AC000, 4);
? ? led_class = class_create(THIS_MODULE, "myled");
? ? device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled"); /* /dev/myled */
? ?
? ? return 0;
}
2.2.6:有入口函數就應該有出口函數:卸載驅動程序時,出口函數調用unregister_chrdev
將映射地址也進行消除
static void __exit led_exit(void)
{
? ? iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
? ? iounmap(GPIO5_GDIR);
? ? iounmap(GPIO5_DR);
? ?
? ? device_destroy(led_class, MKDEV(major, 0));
? ? class_destroy(led_class);
? ?
? ? unregister_chrdev(major, "100ask_led");
}
3.1.7:其他完善:提供設備信息,
自動創建設備節點:class_create, device_creat
/* 7. 其他完善:提供設備信息,自動創建設備節點 */
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
2.3:全部代碼
2.3.1:led_drv.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <asm/io.h>static int major;
static struct class *led_class;/* registers */
// IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 地址:0x02290000 + 0x14
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;// GPIO5_GDIR 地址:0x020AC004
static volatile unsigned int *GPIO5_GDIR;//GPIO5_DR 地址:0x020AC000
static volatile unsigned int *GPIO5_DR;static ssize_t led_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{char val;int ret;/* copy_from_user : get data from app */ret = copy_from_user(&val, buf, 1);/* to set gpio register: out 1/0 */if (val){/* set gpio to let led on */*GPIO5_DR &= ~(1<<3);}else{/* set gpio to let led off */*GPIO5_DR |= (1<<3);}return 1;
}static int led_open(struct inode *inode, struct file *filp)
{/* enable gpio5* configure gpio5_io3 as gpio* configure gpio5_io3 as output */*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x5;*GPIO5_GDIR |= (1<<3);return 0;
}static struct file_operations led_fops = {.owner = THIS_MODULE,.write = led_write,.open = led_open,
};/* 入口函數 */
static int __init led_init(void)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);major = register_chrdev(0, "100ask_led", &led_fops);/* ioremap */// IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 地址:0x02290000 + 0x14IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14, 4);// GPIO5_GDIR 地址:0x020AC004GPIO5_GDIR = ioremap(0x020AC004, 4);//GPIO5_DR 地址:0x020AC000GPIO5_DR = ioremap(0x020AC000, 4);led_class = class_create(THIS_MODULE, "myled");device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled"); /* /dev/myled */return 0;
}static void __exit led_exit(void)
{iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);iounmap(GPIO5_GDIR);iounmap(GPIO5_DR);device_destroy(led_class, MKDEV(major, 0));class_destroy(led_class);unregister_chrdev(major, "100ask_led");
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
2.3.2 led_test.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>// ledtest /dev/myled on
// ledtest /dev/myled offint main(int argc, char **argv)
{int fd;char status = 0;if (argc != 3){printf("Usage: %s <dev> <on|off>\n", argv[0]);printf(" eg: %s /dev/myled on\n", argv[0]);printf(" eg: %s /dev/myled off\n", argv[0]);return -1;}// openfd = open(argv[1], O_RDWR);if (fd < 0){printf("can not open %s\n", argv[0]);return -1;}// writeif (strcmp(argv[2], "on") == 0){status = 1;}write(fd, &status, 1);return 0;
}
2.3.3:Makefile
# 1. 使用不同的開發板內核時, 一定要修改KERN_DIR
# 2. KERN_DIR中的內核要事先配置、編譯, 為了能編譯內核, 要先設置下列環境變量:
# 2.1 ARCH, 比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# 注意: 不同的開發板不同的編譯器上述3個環境變量不一定相同,
# 請參考各開發板的高級用戶使用手冊KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88all:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o ledtest ledtest.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f ledtestobj-m += led_drv.o