/*
* @Author: peter
* @Date: 2024-03-06 19:04:37
* @Last Modified by: peter
* @Last Modified time: 2024-03-07 15:03:52
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/device.h>
#define DTSGPIO_CNT 1 /* 设备号个数 */
#define DTSGPIO_NAME "dtsgpio" /* 名字 */
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *data_addr;
static void __iomem *dirm_addr;
static void __iomem *outen_addr;
static void __iomem *intdis_addr;
static void __iomem *aper_clk_ctrl_addr;
/* dtsgpio设备结构体 */
typedef struct Dtsgpio_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
}TDtsgpio_dev,*PTDtsgpio_dev;
TDtsgpio_dev g_tDtsgpioDev;
static inline void gpio_ioremap(void)
{
data_addr = of_iomap(g_tDtsgpioDev.nd, 0);
dirm_addr = of_iomap(g_tDtsgpioDev.nd, 1);
outen_addr = of_iomap(g_tDtsgpioDev.nd, 2);
intdis_addr = of_iomap(g_tDtsgpioDev.nd, 3);
aper_clk_ctrl_addr = of_iomap(g_tDtsgpioDev.nd, 4);
}
static inline void gpio_iounmap(void)
{
iounmap(data_addr);
iounmap(dirm_addr);
iounmap(outen_addr);
iounmap(intdis_addr);
iounmap(aper_clk_ctrl_addr);
}
/*
* @description : 打开设备
* @param – inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int gpio_open(struct inode *inode, struct file *filp)
{
filp->private_data = &g_tDtsgpioDev; /* 设置私有数据 */
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t gpio_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
return 0;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t gpio_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
int ret;
int val;
char kern_buf[1];
ret = copy_from_user(kern_buf, buf, cnt); // 得到应用层传递过来的数据
if(0 > ret) {
printk(KERN_ERR "kernel write failed!\r\n");
return -EFAULT;
}
val = readl(data_addr);
if (0 == kern_buf[0])
val &= ~(0x1U << 12); // 如果传递过来的数据是0则关闭led
else if (1 == kern_buf[0])
val |= (0x1U << 12); // 如果传递过来的数据是1则点亮led
writel(val, data_addr);
return 0;
}
/*
* @description : 关闭/释放设备
* @param – filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int gpio_release(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations gpio_fops={
.owner =THIS_MODULE,
.open =gpio_open,
.write =gpio_write,
.read =gpio_read,
.release =gpio_release,
};
static int __init gpio_init(void)
{
const char *str;
u32 val;
int ret;
g_tDtsgpioDev.nd = of_find_node_by_path("/gpio");
if(NULL == g_tDtsgpioDev.nd) {
printk(KERN_ERR "gpio node can not found!\r\n");
return -EINVAL;
}
/* 2.读取status属性 */
ret = of_property_read_string(g_tDtsgpioDev.nd, "status", &str);
if(!ret) {
if (strcmp(str, "okay"))
return -EINVAL;
}
/* 3、获取compatible属性值并进行匹配 */
ret = of_property_read_string(g_tDtsgpioDev.nd, "compatible", &str);
if(0 > ret)
return -EINVAL;
if (strcmp(str, "xykj,gpio"))
return -EINVAL;
printk(KERN_ERR "gpio device matching successful!\r\n");
/*4.寄存器内存映射*/
gpio_ioremap();
/* 5.使能GPIO时钟 */
val = readl(aper_clk_ctrl_addr);
val |= (0x1U << 24);
writel(val, aper_clk_ctrl_addr);
/* 6.关闭中断功能 */
val |= (0x1U << 0);
writel(val, intdis_addr);
/* 7.设置GPIO为输出功能 */
val = readl(dirm_addr);
val |= (0x1U << 0);
writel(val, dirm_addr);
/* 8.使能GPIO输出功能 */
val = readl(outen_addr);
val |= (0x1U << 0);
writel(val, outen_addr);
/* 9.初始化LED的默认状态 */
val = readl(data_addr);
ret = of_property_read_string(g_tDtsgpioDev.nd, "default-state", &str);
if(!ret) {
if (!strcmp(str, "on"))
val |= (0x1U << 0);
else
val &= ~(0x1U << 0);
} else
val &= ~(0x1U << 0);
writel(val, data_addr);
if (g_tDtsgpioDev.major)
{
g_tDtsgpioDev.devid=MKDEV(g_tDtsgpioDev.major,0);
ret=register_chrdev_region(g_tDtsgpioDev.devid,DTSGPIO_CNT,DTSGPIO_NAME);
if(ret)
goto out1;
}
else
{
ret=alloc_chrdev_region( &g_tDtsgpioDev.devid,0,DTSGPIO_CNT,DTSGPIO_NAME);
if(ret)
goto out2;
g_tDtsgpioDev.major = MAJOR(g_tDtsgpioDev.devid);
g_tDtsgpioDev.minor = MINOR(g_tDtsgpioDev.devid);
}
printk("dtsgpio major=%d,minor=%d\r\n",g_tDtsgpioDev.major, g_tDtsgpioDev.minor);
/* 初始化cdev */
g_tDtsgpioDev.cdev.owner=THIS_MODULE;
cdev_init(&g_tDtsgpioDev.cdev,&gpio_fops);
/*添加一个cdev*/
ret=cdev_add(&g_tDtsgpioDev.cdev,g_tDtsgpioDev.devid,DTSGPIO_CNT);
if (ret)
goto out2;
/*创建类*/
g_tDtsgpioDev.class=class_create(THIS_MODULE,DTSGPIO_NAME);
if (IS_ERR(g_tDtsgpioDev.class)) {
ret = PTR_ERR(g_tDtsgpioDev.class);
goto out3;
}
/*创建设备*/
g_tDtsgpioDev.device = device_create(g_tDtsgpioDev.class, NULL,
g_tDtsgpioDev.devid, NULL, DTSGPIO_NAME);
if (IS_ERR(g_tDtsgpioDev.device)) {
ret = PTR_ERR(g_tDtsgpioDev.device);
goto out4;
}
printk("gpio_init successful!\r\n");
return 0;
out4:
class_destroy(g_tDtsgpioDev.class);
out3:
cdev_del(&g_tDtsgpioDev.cdev);
out2:
unregister_chrdev_region(g_tDtsgpioDev.devid, DTSGPIO_CNT);
out1:
gpio_iounmap();
return ret;
}
static void __exit gpio_exit(void)
{
/* 注销设备 */
device_destroy(g_tDtsgpioDev.class, g_tDtsgpioDev.devid);
/* 注销类 */
class_destroy(g_tDtsgpioDev.class);
/* 删除cdev */
cdev_del(&g_tDtsgpioDev.cdev);
/* 注销设备号 */
unregister_chrdev_region(g_tDtsgpioDev.devid, DTSGPIO_CNT);
/* 取消地址映射 */
gpio_iounmap();
}
module_init(gpio_init);
module_exit(gpio_exit);
MODULE_AUTHOR("peter 554253547@qq.com");
MODULE_DESCRIPTION("xykj gpio drv!");
MODULE_LICENSE("GPL");