commit 387dfe797b5c6f847ca55e8f63a6c99d9b193372 Author: zhji Date: Sun Feb 8 10:05:25 2026 +0800 [feat] first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f17b0a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.ko +*.o +*.mod +*.mod.c +*.mod.o +*.cmd +*.order +*.symvers diff --git a/led/Makefile b/led/Makefile new file mode 100644 index 0000000..66f01c3 --- /dev/null +++ b/led/Makefile @@ -0,0 +1,23 @@ + +# 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个环境变量不一定相同, +# 请参考各开发板的高级用户使用手册 + +ARCH ?= arm +CROSS_COMPILE ?= /home/jzh/work/study/weidongshan/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/arm-linux- +KERN_DIR = /home/jzh/work/study/weidongshan/100ask_stm32mp157_pro-sdk/Linux-5.4 + +all: + make -C $(KERN_DIR) M=`pwd` ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules + $(CROSS_COMPILE)gcc -o ledtest ledtest.c + +clean: + make -C $(KERN_DIR) M=`pwd` ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules clean + rm -rf modules.order + rm -f ledtest + +obj-m += led_drv.o diff --git a/led/led_drv.c b/led/led_drv.c new file mode 100644 index 0000000..5a589c8 --- /dev/null +++ b/led/led_drv.c @@ -0,0 +1,120 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int major; +static struct class *led_class; + +/* registers */ +// RCC_PLL4CR地址:0x50000000 + 0x894 +static volatile unsigned int *RCC_PLL4CR; + +// RCC_MP_AHB4ENSETR 地址:0x50000000 + 0xA28 +static volatile unsigned int *RCC_MP_AHB4ENSETR; + +// GPIOA_MODER 地址:0x50002000 + 0x00 +static volatile unsigned int *GPIOA_MODER; + +// GPIOA_BSRR 地址: 0x50002000 + 0x18 +static volatile unsigned int *GPIOA_BSRR; + +static ssize_t led_write(struct file *filp, const char __user *buf, + size_t count, loff_t *ppos) +{ + char val; + /* copy_from_user : get data from app */ + copy_from_user(&val, buf, 1); + + /* to set gpio register: out 1/0 */ + if (val) + { + /* set gpa10 to let led on */ + *GPIOA_BSRR = (1<<26); + } + else + { + /* set gpa10 to let led off */ + *GPIOA_BSRR = (1<<10); + } + return 1; +} + +static int led_open(struct inode *inode, struct file *filp) +{ + /* enalbe PLL4, it is clock source for all gpio */ + *RCC_PLL4CR |= (1<<0); + while ((*RCC_PLL4CR & (1<<1)) == 0); + + /* enable gpioA */ + *RCC_MP_AHB4ENSETR |= (1<<0); + + /* + * configure gpa10 as gpio + * configure gpio as output + */ + *GPIOA_MODER &= ~(3<<20); + *GPIOA_MODER |= (1<<20); + + 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(base_phy, size); */ + // RCC_PLL4CR地址:0x50000000 + 0x894 + RCC_PLL4CR = ioremap(0x50000000 + 0x894, 4); + + // RCC_MP_AHB4ENSETR 地址:0x50000000 + 0xA28 + RCC_MP_AHB4ENSETR = ioremap(0x50000000 + 0xA28, 4); + + // GPIOA_MODER 地址:0x50002000 + 0x00 + GPIOA_MODER = ioremap(0x50002000 + 0x00, 4); + + // GPIOA_BSRR 地址: 0x50002000 + 0x18 + GPIOA_BSRR = ioremap(0x50002000 + 0x18, 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(RCC_PLL4CR); + iounmap(RCC_MP_AHB4ENSETR); + iounmap(GPIOA_MODER); + iounmap(GPIOA_BSRR); + + 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"); + + diff --git a/led/ledtest.c b/led/ledtest.c new file mode 100644 index 0000000..90cbfc6 --- /dev/null +++ b/led/ledtest.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include +#include + + +// ledtest /dev/myled on +// ledtest /dev/myled off + +int main(int argc, char **argv) +{ + int fd; + char status = 0; + + if (argc != 3) + { + printf("Usage: %s \n", argv[0]); + printf(" eg: %s /dev/myled on\n", argv[0]); + printf(" eg: %s /dev/myled off\n", argv[0]); + return -1; + } + // open + fd = open(argv[1], O_RDWR); + if (fd < 0) + { + printf("can not open %s\n", argv[0]); + return -1; + } + + // write + if (strcmp(argv[2], "on") == 0) + { + status = 1; + } + + write(fd, &status, 1); + return 0; +} + diff --git a/lishanwen/led_01/Makefile b/lishanwen/led_01/Makefile new file mode 100644 index 0000000..91a8d2e --- /dev/null +++ b/lishanwen/led_01/Makefile @@ -0,0 +1,14 @@ +ARCH ?= arm +CROSS_COMPILE ?= /home/jzh/work/study/weidongshan/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/arm-linux- +KERN_DIR ?= /home/jzh/work/study/weidongshan/100ask_stm32mp157_pro-sdk/Linux-5.4 + +all: + make -C $(KERN_DIR) M=`pwd` ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules + $(CROSS_COMPILE)gcc -o led_app led_app.c + +clean: + make -C $(KERN_DIR) M=`pwd` ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules clean + rm -rf modules.order + rm -f led_app + +obj-m += led_driver.o diff --git a/lishanwen/led_01/led_app.c b/lishanwen/led_01/led_app.c new file mode 100644 index 0000000..bf691ad --- /dev/null +++ b/lishanwen/led_01/led_app.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include + +int main(int argc, char const *argv[]) +{ + int fd; + char status = 0; + + if (argc != 3) { + printf("Usage: %s \r\n", argv[0]); + printf(" eg: %s /dev/led0 on\r\n", argv[0]); + printf(" eg: %s /dev/led0 off\r\n", argv[0]); + return -1; + } + fd = open(argv[1], O_RDWR); + if (fd < 0) { + printf("Cannot open %s\r\n", argv[1]); + return -1; + } + if (strcmp(argv[2], "on") == 0) { + status = 1; + } else if (strcmp(argv[2], "off") == 0) { + status = 0; + } else { + printf("Invalid argument: %s\r\n", argv[2]); + close(fd); + return -1; + } + write(fd, &status, 1); + close(fd); + + return 0; +} diff --git a/lishanwen/led_01/led_driver.c b/lishanwen/led_01/led_driver.c new file mode 100644 index 0000000..62a8d09 --- /dev/null +++ b/lishanwen/led_01/led_driver.c @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static dev_t led_dev; +static struct cdev *led_cdev; +static struct class *led_class; +static struct device *led_device; + +static volatile uint32_t *RCC_PLL4CR; +static volatile uint32_t *RCC_MP_AHB4ENSETR; +static volatile uint32_t *GPIOA_MODER; +static volatile uint32_t *GPIOA_BSRR; + +static int led_open(struct inode *inode, struct file *filp) +{ + *RCC_PLL4CR |= (1 << 0); + while ((*RCC_PLL4CR & (1 << 1)) == 0); + *RCC_MP_AHB4ENSETR |= (1 << 0); + *GPIOA_MODER &= ~(3 << 20); + *GPIOA_MODER |= (1 << 20); + printk(KERN_DEBUG "LED device opened.\r\n"); + + return 0; +} + +static int led_close(struct inode *inode, struct file *filp) +{ + printk(KERN_DEBUG "LED device closed.\r\n"); + return 0; +} + +static int led_write(struct file *filp, const char __user *buff, size_t count, loff_t *offp) +{ + char val; + + copy_from_user(&val, buff, 1); + if (val) { + *GPIOA_BSRR = (1 << 26); + } else { + *GPIOA_BSRR = (1 << 10); + } + + return 0; +} + +static int led_read(struct file *filp, char __user *buff, size_t count, loff_t *offp) +{ + return 0; +} + +static struct file_operations led_ops = { + .owner = THIS_MODULE, + .open = led_open, + .release = led_close, + .write = led_write, + .read = led_read, +}; + +static int __init led_init(void) +{ + int ret; + + led_cdev = cdev_alloc(); + if (led_cdev == NULL) { + printk(KERN_ERR "Failed to allocate cdev\r\n"); + return -ENOMEM; + } + ret = alloc_chrdev_region(&led_dev, 0, 1, "led"); + if (ret != 0) { + printk(KERN_ERR "Failed to allocate chrdev region\r\n"); + return ret; + } + led_cdev->owner = THIS_MODULE; + led_cdev->ops = &led_ops; + cdev_add(led_cdev, led_dev, 1); + led_class = class_create(THIS_MODULE, "led_class"); + if (led_class == NULL) { + printk(KERN_ERR "Failed to create class\r\n"); + return -1; + } + led_device = device_create(led_class, NULL, led_dev, NULL, "led0"); + if (IS_ERR(led_device)) { + printk(KERN_ERR "Failed to create device\r\n"); + return -1; + } + RCC_PLL4CR = ioremap(0x50000000 + 0x894, 4); + RCC_MP_AHB4ENSETR = ioremap(0x50000000 + 0xA28, 4); + GPIOA_MODER = ioremap(0x50002000 + 0x00, 4); + GPIOA_BSRR = ioremap(0x50002000 + 0x18, 4); + + return 0; +} + +static void __exit led_exit(void) +{ + cdev_del(led_cdev); + unregister_chrdev_region(led_dev, 1); + device_destroy(led_class, led_dev); + class_destroy(led_class); + iounmap(RCC_PLL4CR); + iounmap(RCC_MP_AHB4ENSETR); + iounmap(GPIOA_MODER); + iounmap(GPIOA_BSRR); +} + +module_init(led_init); +module_exit(led_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("jzhgonha@163.com"); +MODULE_VERSION("V0.1"); +MODULE_DESCRIPTION("A simple LED driver for STM32MP157"); diff --git a/lishanwen/led_02_dts/Makefile b/lishanwen/led_02_dts/Makefile new file mode 100644 index 0000000..91a8d2e --- /dev/null +++ b/lishanwen/led_02_dts/Makefile @@ -0,0 +1,14 @@ +ARCH ?= arm +CROSS_COMPILE ?= /home/jzh/work/study/weidongshan/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/arm-linux- +KERN_DIR ?= /home/jzh/work/study/weidongshan/100ask_stm32mp157_pro-sdk/Linux-5.4 + +all: + make -C $(KERN_DIR) M=`pwd` ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules + $(CROSS_COMPILE)gcc -o led_app led_app.c + +clean: + make -C $(KERN_DIR) M=`pwd` ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules clean + rm -rf modules.order + rm -f led_app + +obj-m += led_driver.o diff --git a/lishanwen/led_02_dts/led_app.c b/lishanwen/led_02_dts/led_app.c new file mode 100644 index 0000000..bf691ad --- /dev/null +++ b/lishanwen/led_02_dts/led_app.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include + +int main(int argc, char const *argv[]) +{ + int fd; + char status = 0; + + if (argc != 3) { + printf("Usage: %s \r\n", argv[0]); + printf(" eg: %s /dev/led0 on\r\n", argv[0]); + printf(" eg: %s /dev/led0 off\r\n", argv[0]); + return -1; + } + fd = open(argv[1], O_RDWR); + if (fd < 0) { + printf("Cannot open %s\r\n", argv[1]); + return -1; + } + if (strcmp(argv[2], "on") == 0) { + status = 1; + } else if (strcmp(argv[2], "off") == 0) { + status = 0; + } else { + printf("Invalid argument: %s\r\n", argv[2]); + close(fd); + return -1; + } + write(fd, &status, 1); + close(fd); + + return 0; +} diff --git a/lishanwen/led_02_dts/led_driver.c b/lishanwen/led_02_dts/led_driver.c new file mode 100644 index 0000000..075e2e6 --- /dev/null +++ b/lishanwen/led_02_dts/led_driver.c @@ -0,0 +1,150 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static dev_t led_dev; +static struct cdev *led_cdev; +static struct class *led_class; +static struct device *led_device; + +static volatile uint32_t *RCC_PLL4CR; +static volatile uint32_t *RCC_MP_AHB4ENSETR; +static volatile uint32_t *GPIOA_MODER; +static volatile uint32_t *GPIOA_BSRR; + +static int led_open(struct inode *inode, struct file *filp) +{ + *RCC_PLL4CR |= (1 << 0); + while ((*RCC_PLL4CR & (1 << 1)) == 0); + *RCC_MP_AHB4ENSETR |= (1 << 0); + *GPIOA_MODER &= ~(3 << 20); + *GPIOA_MODER |= (1 << 20); + printk(KERN_DEBUG "LED device opened.\r\n"); + + return 0; +} + +static int led_close(struct inode *inode, struct file *filp) +{ + printk(KERN_DEBUG "LED device closed.\r\n"); + return 0; +} + +static int led_write(struct file *filp, const char __user *buff, size_t count, loff_t *offp) +{ + char val; + + copy_from_user(&val, buff, 1); + if (val) { + *GPIOA_BSRR = (1 << 26); + } else { + *GPIOA_BSRR = (1 << 10); + } + + return 0; +} + +static int led_read(struct file *filp, char __user *buff, size_t count, loff_t *offp) +{ + return 0; +} + +static struct file_operations led_ops = { + .owner = THIS_MODULE, + .open = led_open, + .release = led_close, + .write = led_write, + .read = led_read, +}; + +static int __init led_init(void) +{ + int ret; + struct device_node *led_node; + uint32_t addr_rcc_pll4cr; + uint32_t addr_rcc_mp_ahb4ensetr; + uint32_t addr_gpioa_moder; + uint32_t addr_gpioa_bsrr; + + led_cdev = cdev_alloc(); + if (led_cdev == NULL) { + printk(KERN_ERR "Failed to allocate cdev\r\n"); + return -ENOMEM; + } + ret = alloc_chrdev_region(&led_dev, 0, 1, "led"); + if (ret != 0) { + printk(KERN_ERR "Failed to allocate chrdev region\r\n"); + return ret; + } + led_cdev->owner = THIS_MODULE; + led_cdev->ops = &led_ops; + cdev_add(led_cdev, led_dev, 1); + led_class = class_create(THIS_MODULE, "led_class"); + if (led_class == NULL) { + printk(KERN_ERR "Failed to create class\r\n"); + return -1; + } + led_device = device_create(led_class, NULL, led_dev, NULL, "led0"); + if (IS_ERR(led_device)) { + printk(KERN_ERR "Failed to create device\r\n"); + return -1; + } + led_node = of_find_node_by_path("/jzhled"); + if (led_node == NULL) { + printk(KERN_ERR "file=%s, line=%d\r\n", __FILE__, __LINE__); + goto no_led_node; + } + if (of_property_read_u32_index(led_node, "reg", 0, &addr_rcc_pll4cr)) { + printk(KERN_ERR "file=%s, line=%d\r\n", __FILE__, __LINE__); + goto of_error; + } + if (of_property_read_u32_index(led_node, "reg", 2, &addr_rcc_mp_ahb4ensetr)) { + printk(KERN_ERR "file=%s, line=%d\r\n", __FILE__, __LINE__); + goto of_error; + } + if (of_property_read_u32_index(led_node, "reg", 4, &addr_gpioa_moder)) { + printk(KERN_ERR "file=%s, line=%d\r\n", __FILE__, __LINE__); + goto of_error; + } + if (of_property_read_u32_index(led_node, "reg", 6, &addr_gpioa_bsrr)) { + printk(KERN_ERR "file=%s, line=%d\r\n", __FILE__, __LINE__); + goto of_error; + } + RCC_PLL4CR = ioremap(addr_rcc_pll4cr, 4); + RCC_MP_AHB4ENSETR = ioremap(addr_rcc_mp_ahb4ensetr, 4); + GPIOA_MODER = ioremap(addr_gpioa_moder, 4); + GPIOA_BSRR = ioremap(addr_gpioa_bsrr, 4); + return 0; + + of_error: + printk(KERN_ERR "Failed to get led node reg\r\n"); + return -1; + no_led_node: + printk(KERN_ERR "No led node found\r\n"); + return -1; +} + +static void __exit led_exit(void) +{ + cdev_del(led_cdev); + unregister_chrdev_region(led_dev, 1); + device_destroy(led_class, led_dev); + class_destroy(led_class); + iounmap(RCC_PLL4CR); + iounmap(RCC_MP_AHB4ENSETR); + iounmap(GPIOA_MODER); + iounmap(GPIOA_BSRR); +} + +module_init(led_init); +module_exit(led_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("jzhgonha@163.com"); +MODULE_VERSION("V0.1"); +MODULE_DESCRIPTION("A simple LED driver for STM32MP157"); diff --git a/lishanwen/led_platform/Makefile b/lishanwen/led_platform/Makefile new file mode 100644 index 0000000..881f707 --- /dev/null +++ b/lishanwen/led_platform/Makefile @@ -0,0 +1,15 @@ +ARCH ?= arm +CROSS_COMPILE ?= /home/jzh/work/study/weidongshan/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/arm-linux- +KERN_DIR ?= /home/jzh/work/study/weidongshan/100ask_stm32mp157_pro-sdk/Linux-5.4 + +all: + make -C $(KERN_DIR) M=`pwd` ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules + $(CROSS_COMPILE)gcc -o led_app led_app.c + +clean: + make -C $(KERN_DIR) M=`pwd` ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules clean + rm -rf modules.order + rm -f led_app + +obj-m += led_driver.o +obj-m += led_device.o diff --git a/lishanwen/led_platform/led_app.c b/lishanwen/led_platform/led_app.c new file mode 100644 index 0000000..c3f87c5 --- /dev/null +++ b/lishanwen/led_platform/led_app.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include + +int main(int argc, char const *argv[]) +{ + int fd; + char status = 0; + + if (argc != 3) { + printf("Usage: %s \r\n", argv[0]); + printf(" eg: %s /dev/led_jzh on\r\n", argv[0]); + printf(" eg: %s /dev/led_jzh off\r\n", argv[0]); + return -1; + } + fd = open(argv[1], O_RDWR); + if (fd < 0) { + printf("Cannot open %s\r\n", argv[1]); + return -1; + } + if (strcmp(argv[2], "on") == 0) { + status = 1; + } else if (strcmp(argv[2], "off") == 0) { + status = 0; + } else { + printf("Invalid argument: %s\r\n", argv[2]); + close(fd); + return -1; + } + write(fd, &status, 1); + close(fd); + + return 0; +} diff --git a/lishanwen/led_platform/led_device.c b/lishanwen/led_platform/led_device.c new file mode 100644 index 0000000..6ed649b --- /dev/null +++ b/lishanwen/led_platform/led_device.c @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct resource led_resource[] = { + [0] = { + .start = 0x50000000 + 0x894, + .end = 0x50000000 + 0x894 + 3, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = 0x50000000 + 0xA28, + .end = 0x50000000 + 0xA28 + 3, + .flags = IORESOURCE_MEM, + }, + [2] = { + .start = 0x50002000 + 0x00, + .end = 0x50002000 + 0x00 + 3, + .flags = IORESOURCE_MEM, + }, + [3] = { + .start = 0x50002000 + 0x18, + .end = 0x50002000 + 0x18 + 3, + .flags = IORESOURCE_MEM, + }, +}; + +static void led_release(struct device *dev) +{ + /* do nothing */ +} + +static struct platform_device led_device = { + .name = "led_jzh", + .id = -1, + .num_resources = ARRAY_SIZE(led_resource), + .resource = led_resource, + .dev = { + .release = led_release, + }, +}; + +static int __init led_device_init(void) +{ + platform_device_register(&led_device); + + return 0; +} + +static void __exit led_device_exit(void) +{ + platform_device_unregister(&led_device); +} + +module_init(led_device_init); +module_exit(led_device_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("jzhgonha@163.com"); +MODULE_VERSION("V0.1"); +MODULE_DESCRIPTION("A simple LED driver for STM32MP157"); diff --git a/lishanwen/led_platform/led_driver.c b/lishanwen/led_platform/led_driver.c new file mode 100644 index 0000000..c0234c8 --- /dev/null +++ b/lishanwen/led_platform/led_driver.c @@ -0,0 +1,162 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static dev_t led_dev; +static struct cdev *led_cdev; +static struct class *led_class; +static struct device *led_device; + +static volatile uint32_t *RCC_PLL4CR; +static volatile uint32_t *RCC_MP_AHB4ENSETR; +static volatile uint32_t *GPIOA_MODER; +static volatile uint32_t *GPIOA_BSRR; + +static int led_open(struct inode *inode, struct file *filp) +{ + *RCC_PLL4CR |= (1 << 0); + while ((*RCC_PLL4CR & (1 << 1)) == 0); + *RCC_MP_AHB4ENSETR |= (1 << 0); + *GPIOA_MODER &= ~(3 << 20); + *GPIOA_MODER |= (1 << 20); + printk(KERN_DEBUG "LED device opened.\r\n"); + + return 0; +} + +static int led_close(struct inode *inode, struct file *filp) +{ + printk(KERN_DEBUG "LED device closed.\r\n"); + return 0; +} + +static int led_write(struct file *filp, const char __user *buff, size_t count, loff_t *offp) +{ + char val; + + copy_from_user(&val, buff, 1); + if (val) { + *GPIOA_BSRR = (1 << 26); + } else { + *GPIOA_BSRR = (1 << 10); + } + + return 0; +} + +static int led_read(struct file *filp, char __user *buff, size_t count, loff_t *offp) +{ + return 0; +} + +static struct file_operations led_ops = { + .owner = THIS_MODULE, + .open = led_open, + .release = led_close, + .write = led_write, + .read = led_read, +}; + +static int led_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret; + + printk(KERN_ERR "led_probe, %d\r\n", __LINE__); + led_cdev = cdev_alloc(); + if (led_cdev == NULL) { + printk(KERN_ERR "Failed to allocate cdev\r\n"); + return -ENOMEM; + } + ret = alloc_chrdev_region(&led_dev, 0, 1, "led"); + if (ret != 0) { + printk(KERN_ERR "Failed to allocate chrdev region\r\n"); + return ret; + } + led_cdev->owner = THIS_MODULE; + led_cdev->ops = &led_ops; + cdev_add(led_cdev, led_dev, 1); + led_class = class_create(THIS_MODULE, "led_class"); + if (led_class == NULL) { + printk(KERN_ERR "Failed to create class\r\n"); + return -1; + } + led_device = device_create(led_class, NULL, led_dev, NULL, "led_jzh"); + if (IS_ERR(led_device)) { + printk(KERN_ERR "Failed to create device\r\n"); + return -1; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + RCC_PLL4CR = ioremap(res->start, res->end - res->start + 1); + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + RCC_MP_AHB4ENSETR = ioremap(res->start, res->end - res->start + 1); + res = platform_get_resource(pdev, IORESOURCE_MEM, 2); + GPIOA_MODER = ioremap(res->start, res->end - res->start + 1); + res = platform_get_resource(pdev, IORESOURCE_MEM, 3); + GPIOA_BSRR = ioremap(res->start, res->end - res->start + 1); + + return 0; +} + +static int led_remove(struct platform_device *pdev) +{ + cdev_del(led_cdev); + unregister_chrdev_region(led_dev, 1); + device_destroy(led_class, led_dev); + class_destroy(led_class); + iounmap(RCC_PLL4CR); + iounmap(RCC_MP_AHB4ENSETR); + iounmap(GPIOA_MODER); + iounmap(GPIOA_BSRR); + + return 0; +} + +static const struct of_device_id led_of_match[] = { + { .compatible = "led_jzh", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, led_of_match); + +static struct platform_driver led_driver = { + .probe = led_probe, + .remove = led_remove, + .driver = { + .name = "led_jzh", + .of_match_table = led_of_match, + }, +}; + +static int __init led_driver_init(void) +{ + int ret; + ret = platform_driver_register(&led_driver); + if (ret) { + printk(KERN_ERR "Failed to register LED platform driver\r\n"); + return ret; + } else { + printk(KERN_ERR "Succeeded to register LED platform driver\r\n"); + } + return 0; +} + +static void __exit led_driver_exit(void) +{ + platform_driver_unregister(&led_driver); +} + +module_init(led_driver_init); +module_exit(led_driver_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("jzhgonha@163.com"); +MODULE_VERSION("V0.1"); +MODULE_DESCRIPTION("A simple LED driver for STM32MP157"); diff --git a/pinctrl/pinctrl_virtual/Makefile b/pinctrl/pinctrl_virtual/Makefile new file mode 100644 index 0000000..d17a573 --- /dev/null +++ b/pinctrl/pinctrl_virtual/Makefile @@ -0,0 +1,13 @@ +ARCH ?= arm +CROSS_COMPILE ?= /home/jzh/work/study/weidongshan/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/arm-linux- +KERN_DIR ?= /home/jzh/work/study/weidongshan/100ask_stm32mp157_pro-sdk/Linux-5.4 + +all: + make -C $(KERN_DIR) M=`pwd` ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules + +clean: + make -C $(KERN_DIR) M=`pwd` ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules clean + rm -rf modules.order + +obj-m += pinctrl_virtual_driver.o +obj-m += pinctrl_virtual_client.o diff --git a/pinctrl/pinctrl_virtual/core.h b/pinctrl/pinctrl_virtual/core.h new file mode 100644 index 0000000..7f34167 --- /dev/null +++ b/pinctrl/pinctrl_virtual/core.h @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Core private header for the pin control subsystem + * + * Copyright (C) 2011 ST-Ericsson SA + * Written on behalf of Linaro for ST-Ericsson + * + * Author: Linus Walleij + */ + +#include +#include +#include +#include +#include + +struct pinctrl_gpio_range; + +/** + * struct pinctrl_dev - pin control class device + * @node: node to include this pin controller in the global pin controller list + * @desc: the pin controller descriptor supplied when initializing this pin + * controller + * @pin_desc_tree: each pin descriptor for this pin controller is stored in + * this radix tree + * @pin_group_tree: optionally each pin group can be stored in this radix tree + * @num_groups: optionally number of groups can be kept here + * @pin_function_tree: optionally each function can be stored in this radix tree + * @num_functions: optionally number of functions can be kept here + * @gpio_ranges: a list of GPIO ranges that is handled by this pin controller, + * ranges are added to this list at runtime + * @dev: the device entry for this pin controller + * @owner: module providing the pin controller, used for refcounting + * @driver_data: driver data for drivers registering to the pin controller + * subsystem + * @p: result of pinctrl_get() for this device + * @hog_default: default state for pins hogged by this device + * @hog_sleep: sleep state for pins hogged by this device + * @mutex: mutex taken on each pin controller specific action + * @device_root: debugfs root for this device + */ +struct pinctrl_dev { + struct list_head node; + struct pinctrl_desc *desc; + struct radix_tree_root pin_desc_tree; +#ifdef CONFIG_GENERIC_PINCTRL_GROUPS + struct radix_tree_root pin_group_tree; + unsigned int num_groups; +#endif +#ifdef CONFIG_GENERIC_PINMUX_FUNCTIONS + struct radix_tree_root pin_function_tree; + unsigned int num_functions; +#endif + struct list_head gpio_ranges; + struct device *dev; + struct module *owner; + void *driver_data; + struct pinctrl *p; + struct pinctrl_state *hog_default; + struct pinctrl_state *hog_sleep; + struct mutex mutex; +#ifdef CONFIG_DEBUG_FS + struct dentry *device_root; +#endif +}; + +/** + * struct pinctrl - per-device pin control state holder + * @node: global list node + * @dev: the device using this pin control handle + * @states: a list of states for this device + * @state: the current state + * @dt_maps: the mapping table chunks dynamically parsed from device tree for + * this device, if any + * @users: reference count + */ +struct pinctrl { + struct list_head node; + struct device *dev; + struct list_head states; + struct pinctrl_state *state; + struct list_head dt_maps; + struct kref users; +}; + +/** + * struct pinctrl_state - a pinctrl state for a device + * @node: list node for struct pinctrl's @states field + * @name: the name of this state + * @settings: a list of settings for this state + */ +struct pinctrl_state { + struct list_head node; + const char *name; + struct list_head settings; +}; + +/** + * struct pinctrl_setting_mux - setting data for MAP_TYPE_MUX_GROUP + * @group: the group selector to program + * @func: the function selector to program + */ +struct pinctrl_setting_mux { + unsigned group; + unsigned func; +}; + +/** + * struct pinctrl_setting_configs - setting data for MAP_TYPE_CONFIGS_* + * @group_or_pin: the group selector or pin ID to program + * @configs: a pointer to an array of config parameters/values to program into + * hardware. Each individual pin controller defines the format and meaning + * of config parameters. + * @num_configs: the number of entries in array @configs + */ +struct pinctrl_setting_configs { + unsigned group_or_pin; + unsigned long *configs; + unsigned num_configs; +}; + +/** + * struct pinctrl_setting - an individual mux or config setting + * @node: list node for struct pinctrl_settings's @settings field + * @type: the type of setting + * @pctldev: pin control device handling to be programmed. Not used for + * PIN_MAP_TYPE_DUMMY_STATE. + * @dev_name: the name of the device using this state + * @data: Data specific to the setting type + */ +struct pinctrl_setting { + struct list_head node; + enum pinctrl_map_type type; + struct pinctrl_dev *pctldev; + const char *dev_name; + union { + struct pinctrl_setting_mux mux; + struct pinctrl_setting_configs configs; + } data; +}; + +/** + * struct pin_desc - pin descriptor for each physical pin in the arch + * @pctldev: corresponding pin control device + * @name: a name for the pin, e.g. the name of the pin/pad/finger on a + * datasheet or such + * @dynamic_name: if the name of this pin was dynamically allocated + * @drv_data: driver-defined per-pin data. pinctrl core does not touch this + * @mux_usecount: If zero, the pin is not claimed, and @owner should be NULL. + * If non-zero, this pin is claimed by @owner. This field is an integer + * rather than a boolean, since pinctrl_get() might process multiple + * mapping table entries that refer to, and hence claim, the same group + * or pin, and each of these will increment the @usecount. + * @mux_owner: The name of device that called pinctrl_get(). + * @mux_setting: The most recent selected mux setting for this pin, if any. + * @gpio_owner: If pinctrl_gpio_request() was called for this pin, this is + * the name of the GPIO that "owns" this pin. + */ +struct pin_desc { + struct pinctrl_dev *pctldev; + const char *name; + bool dynamic_name; + void *drv_data; + /* These fields only added when supporting pinmux drivers */ +#ifdef CONFIG_PINMUX + unsigned mux_usecount; + const char *mux_owner; + const struct pinctrl_setting_mux *mux_setting; + const char *gpio_owner; +#endif +}; + +/** + * struct pinctrl_maps - a list item containing part of the mapping table + * @node: mapping table list node + * @maps: array of mapping table entries + * @num_maps: the number of entries in @maps + */ +struct pinctrl_maps { + struct list_head node; + const struct pinctrl_map *maps; + unsigned num_maps; +}; + +#ifdef CONFIG_GENERIC_PINCTRL_GROUPS + +/** + * struct group_desc - generic pin group descriptor + * @name: name of the pin group + * @pins: array of pins that belong to the group + * @num_pins: number of pins in the group + * @data: pin controller driver specific data + */ +struct group_desc { + const char *name; + int *pins; + int num_pins; + void *data; +}; + +int pinctrl_generic_get_group_count(struct pinctrl_dev *pctldev); + +const char *pinctrl_generic_get_group_name(struct pinctrl_dev *pctldev, + unsigned int group_selector); + +int pinctrl_generic_get_group_pins(struct pinctrl_dev *pctldev, + unsigned int group_selector, + const unsigned int **pins, + unsigned int *npins); + +struct group_desc *pinctrl_generic_get_group(struct pinctrl_dev *pctldev, + unsigned int group_selector); + +int pinctrl_generic_add_group(struct pinctrl_dev *pctldev, const char *name, + int *gpins, int ngpins, void *data); + +int pinctrl_generic_remove_group(struct pinctrl_dev *pctldev, + unsigned int group_selector); + +#endif /* CONFIG_GENERIC_PINCTRL_GROUPS */ + +struct pinctrl_dev *get_pinctrl_dev_from_devname(const char *dev_name); +struct pinctrl_dev *get_pinctrl_dev_from_of_node(struct device_node *np); +int pin_get_from_name(struct pinctrl_dev *pctldev, const char *name); +const char *pin_get_name(struct pinctrl_dev *pctldev, const unsigned pin); +int pinctrl_get_group_selector(struct pinctrl_dev *pctldev, + const char *pin_group); + +static inline struct pin_desc *pin_desc_get(struct pinctrl_dev *pctldev, + unsigned int pin) +{ + return radix_tree_lookup(&pctldev->pin_desc_tree, pin); +} + +extern struct pinctrl_gpio_range * +pinctrl_find_gpio_range_from_pin_nolock(struct pinctrl_dev *pctldev, + unsigned int pin); + +int pinctrl_register_map(const struct pinctrl_map *maps, unsigned num_maps, + bool dup); +void pinctrl_unregister_map(const struct pinctrl_map *map); + +extern int pinctrl_force_sleep(struct pinctrl_dev *pctldev); +extern int pinctrl_force_default(struct pinctrl_dev *pctldev); + +extern struct mutex pinctrl_maps_mutex; +extern struct list_head pinctrl_maps; + +#define for_each_maps(_maps_node_, _i_, _map_) \ + list_for_each_entry(_maps_node_, &pinctrl_maps, node) \ + for (_i_ = 0, _map_ = &_maps_node_->maps[_i_]; \ + _i_ < _maps_node_->num_maps; \ + _i_++, _map_ = &_maps_node_->maps[_i_]) diff --git a/pinctrl/pinctrl_virtual/pinctrl_virtual.dts b/pinctrl/pinctrl_virtual/pinctrl_virtual.dts new file mode 100644 index 0000000..c65ad54 --- /dev/null +++ b/pinctrl/pinctrl_virtual/pinctrl_virtual.dts @@ -0,0 +1,13 @@ + virtual_pin_controller { + compatible = "jzh,pinctrl-virtual"; + i2cgrp: i2cgrp { + functions = "i2c", "i2c"; + groups = "pin0", "pin1"; + configs = <0x11223344 0x55667788>; + }; + }; + virtual_i2c { + compatible = "jzh,pinctrl-client"; + pinctrl-names = "default"; + pinctrl-0 = <&i2cgrp>; + }; diff --git a/pinctrl/pinctrl_virtual/pinctrl_virtual_client.c b/pinctrl/pinctrl_virtual/pinctrl_virtual_client.c new file mode 100644 index 0000000..9f6ae54 --- /dev/null +++ b/pinctrl/pinctrl_virtual/pinctrl_virtual_client.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct of_device_id pinctrl_client_of_match[] = { + { .compatible = "jzh,pinctrl-client", }, + { }, +}; + +static int pinctrl_client_probe(struct platform_device *pdev) +{ + printk("%s %s %d\r\n", __FILE__, __FUNCTION__, __LINE__); + return 0; +} + +static int pinctrl_client_remove(struct platform_device *pdev) +{ + printk("%s %s %d\r\n", __FILE__, __FUNCTION__, __LINE__); + return 0; +} + +static struct platform_driver pinctrl_client_driver = { + .probe = pinctrl_client_probe, + .remove = pinctrl_client_remove, + .driver = { + .name = "pinctrl-client", + .of_match_table = of_match_ptr(pinctrl_client_of_match), + }, +}; + +static int __init pinctrl_client_init(void) +{ + printk("%s %s %d\r\n", __FILE__, __FUNCTION__, __LINE__); + return platform_driver_register(&pinctrl_client_driver); +} + +static void __exit pinctrl_client_exit(void) +{ + printk("%s %s %d\r\n", __FILE__, __FUNCTION__, __LINE__); + platform_driver_unregister(&pinctrl_client_driver); +} + +module_init(pinctrl_client_init); +module_exit(pinctrl_client_exit); +MODULE_DESCRIPTION("Virtual Pin Control Driver"); +MODULE_AUTHOR("jzhgonha@163.com"); +MODULE_LICENSE("GPL"); diff --git a/pinctrl/pinctrl_virtual/pinctrl_virtual_driver.c b/pinctrl/pinctrl_virtual/pinctrl_virtual_driver.c new file mode 100644 index 0000000..901070c --- /dev/null +++ b/pinctrl/pinctrl_virtual/pinctrl_virtual_driver.c @@ -0,0 +1,273 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "core.h" + +static struct pinctrl_dev *g_pinctrl_dev; + +static const struct pinctrl_pin_desc pins[] = { + {0, "pin0", NULL}, + {1, "pin1", NULL}, + {2, "pin2", NULL}, + {3, "pin3", NULL}, +}; + +struct virtual_functions_desc { + const char *name; + const char **groups; + int num_groups; +}; + +static const char *func0_grps[] = {"pin0", "pin1", "pin2", "pin3"}; +static const char *func1_grps[] = {"pin0", "pin1"}; +static const char *func2_grps[] = {"pin2", "pin3"}; + +static const struct virtual_functions_desc g_funcs_desc[] = { + {"gpio", func0_grps, 4}, + {"i2c", func1_grps, 2}, + {"uart", func2_grps, 2}, +}; + +static unsigned long g_configs[4]; + +static int virtual_pctrl_get_groups_count(struct pinctrl_dev *pctldev) +{ + return pctldev->desc->npins; +} + +static const char *virtual_pctrl_get_group_name(struct pinctrl_dev *pctldev, unsigned selector) +{ + return pctldev->desc->pins[selector].name; +} + +static int virtual_pctrl_get_group_pins(struct pinctrl_dev *pctldev, unsigned selector, const unsigned **pins, unsigned *npins) +{ + if (selector >= pctldev->desc->npins) + return -EINVAL; + *pins = &pctldev->desc->pins[selector].number; + *npins = 1; + return 0; +} + +static void virtual_pctrl_pin_dbg_show(struct pinctrl_dev *pctldev, struct seq_file *s, unsigned offset) +{ + seq_printf(s, "%s\r\n", dev_name(pctldev->dev)); +} + +static int virtual_pctrl_dt_node_to_map(struct pinctrl_dev *pctldev, struct device_node *np, struct pinctrl_map **map, unsigned *num_maps) +{ + int i; + int num_pins = 0; + const char *pin; + const char *function; + uint32_t config; + struct pinctrl_map *new_maps; + unsigned long *configs; + + while (1) { + if (of_property_read_string_index(np, "groups", num_pins, &pin)) + break; + num_pins++; + } + new_maps = kmalloc(sizeof(struct pinctrl_map) * num_pins * 2, GFP_KERNEL); + for (i = 0; i < num_pins; i++) { + of_property_read_string_index(np, "groups", i, &pin); + of_property_read_string_index(np, "functions", i, &function); + of_property_read_u32_index(np, "configs", i, &config); + + configs = kmalloc(sizeof(*configs), GFP_KERNEL); + + new_maps[i * 2].type = PIN_MAP_TYPE_MUX_GROUP; + new_maps[i * 2].data.mux.function = function; + new_maps[i * 2].data.mux.group = pin; + + new_maps[i * 2 + 1].type = PIN_MAP_TYPE_CONFIGS_GROUP; + new_maps[i * 2 + 1].data.configs.group_or_pin = pin; + new_maps[i * 2 + 1].data.configs.configs = configs; + configs[0] = config; + new_maps[i * 2 + 1].data.configs.num_configs = 1; + } + *map = new_maps; + *num_maps = num_pins * 2; + return 0; +} + +static void virtual_pctrl_dt_free_map(struct pinctrl_dev *pctldev, struct pinctrl_map *map, unsigned num_maps) +{ + while (num_maps--) { + if (map->type == PIN_MAP_TYPE_CONFIGS_PIN) + kfree(map->data.configs.configs); + kfree(map); + map++; + } +} + +static const struct pinctrl_ops virtual_pctrl_ops = { + .get_groups_count = virtual_pctrl_get_groups_count, + .get_group_name = virtual_pctrl_get_group_name, + .get_group_pins = virtual_pctrl_get_group_pins, + .pin_dbg_show = virtual_pctrl_pin_dbg_show, + .dt_node_to_map = virtual_pctrl_dt_node_to_map, + .dt_free_map = virtual_pctrl_dt_free_map, +}; + +static int virtual_pmx_get_funcs_count(struct pinctrl_dev *pctldev) +{ + return ARRAY_SIZE(g_funcs_desc); +} + +static const char *virtual_pmx_get_func_name(struct pinctrl_dev *pctldev, unsigned selector) +{ + return g_funcs_desc[selector].name; +} + +static int virtual_pmx_get_groups(struct pinctrl_dev *pctldev, unsigned selector, const char * const **groups, unsigned * const num_groups) +{ + *groups = g_funcs_desc[selector].groups; + *num_groups = g_funcs_desc[selector].num_groups; + return 0; +} + +static int virtual_pmx_set_mux(struct pinctrl_dev *pctldev, unsigned selector, unsigned group) +{ + printk("set %s as %s\r\n", pctldev->desc->pins[group].name, g_funcs_desc[selector].name); + return 0; +} + +static const struct pinmux_ops virtual_pmx_ops = { + .get_functions_count = virtual_pmx_get_funcs_count, + .get_function_name = virtual_pmx_get_func_name, + .get_function_groups = virtual_pmx_get_groups, + .set_mux = virtual_pmx_set_mux, + .gpio_set_direction = NULL, +}; + +static int virtual_pinconf_get(struct pinctrl_dev *pctldev, unsigned pin_id, unsigned long *config) +{ + *config = g_configs[pin_id]; + return 0; +} + +static int virtual_pinconf_set(struct pinctrl_dev *pctldev, unsigned pin_id, unsigned long *config, unsigned num_configs) +{ + if (num_configs != 1) + return -EINVAL; + g_configs[pin_id] = *config; + printk("config %s as 0x%lX\r\n", pctldev->desc->pins[pin_id].name, *config); + return 0; +} + +static int virtual_pinconf_group_get(struct pinctrl_dev *pctldev, + unsigned group, + unsigned long *config) +{ + return virtual_pinconf_get(pctldev, group, config); +} + +static int virtual_pinconf_group_set(struct pinctrl_dev *pctldev, + unsigned group, + unsigned long *configs, + unsigned num_configs) +{ + int ret = 0; + unsigned i; + + for (i = 0; i < num_configs; i++) { + ret = virtual_pinconf_set(pctldev, group, &configs[i], 1); + if (ret) + break; + } + + printk("config group %s as 0x%lX\n", + pctldev->desc->pins[group].name, + num_configs > 0 ? configs[0] : 0); + return ret; +} + +static void virtual_pinconf_dbg_show(struct pinctrl_dev *pctldev, struct seq_file *s, unsigned pin_id) +{ + seq_printf(s, "0x%lX\r\n", g_configs[pin_id]); +} + +static void virtual_pinconf_group_dbg_show(struct pinctrl_dev *pctldev, struct seq_file *s, unsigned pin_id) +{ + seq_printf(s, "0x%lX\r\n", g_configs[pin_id]); +} + +static const struct pinconf_ops virtual_pinconf_ops = { + .pin_config_get = virtual_pinconf_get, + .pin_config_set = virtual_pinconf_set, + .pin_config_group_get = virtual_pinconf_group_get, + .pin_config_group_set = virtual_pinconf_group_set, + .pin_config_dbg_show = virtual_pinconf_dbg_show, + .pin_config_group_dbg_show = virtual_pinconf_group_dbg_show, +}; + +static const struct of_device_id pinctrl_virtual_of_match[] = { + { .compatible = "jzh,pinctrl-virtual", }, + { }, +}; + +static int pinctrl_virtual_probe(struct platform_device *pdev) +{ + struct pinctrl_desc *pinctrl; + + printk("%s %s %d\r\n", __FILE__, __FUNCTION__, __LINE__); + + pinctrl = devm_kzalloc(&pdev->dev, sizeof(*pinctrl), GFP_KERNEL); + pinctrl->name = dev_name(&pdev->dev); + pinctrl->owner = THIS_MODULE; + pinctrl->pins = pins; + pinctrl->npins = ARRAY_SIZE(pins); + pinctrl->pctlops = &virtual_pctrl_ops; + pinctrl->pmxops = &virtual_pmx_ops; + pinctrl->confops = &virtual_pinconf_ops; + g_pinctrl_dev = devm_pinctrl_register(&pdev->dev, pinctrl, NULL); + + return 0; +} + +static int pinctrl_virtual_remove(struct platform_device *pdev) +{ + printk("%s %s %d\r\n", __FILE__, __FUNCTION__, __LINE__); + return 0; +} + +static struct platform_driver pinctrl_virtual_driver = { + .probe = pinctrl_virtual_probe, + .remove = pinctrl_virtual_remove, + .driver = { + .name = "pinctrl-virtual", + .of_match_table = of_match_ptr(pinctrl_virtual_of_match), + }, +}; + +static int __init pinctrl_virtual_init(void) +{ + printk("%s %s %d\r\n", __FILE__, __FUNCTION__, __LINE__); + return platform_driver_register(&pinctrl_virtual_driver); +} + +static void __exit pinctrl_virtual_exit(void) +{ + printk("%s %s %d\r\n", __FILE__, __FUNCTION__, __LINE__); + platform_driver_unregister(&pinctrl_virtual_driver); +} + +module_init(pinctrl_virtual_init); +module_exit(pinctrl_virtual_exit); +MODULE_DESCRIPTION("Virtual Pin Control Driver"); +MODULE_AUTHOR("jzhgonha@163.com"); +MODULE_LICENSE("GPL");