思路
1、创建字符设备
2、创建于字符设备关联的sys文件系统
3、在用户空间,通关串口命令查看驱动状态
使用示例
代码
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/miscdevice.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/of_platform.h>
#include <linux/string.h>
#include <linux/err.h>
#include <linux/pinctrl/pinconf-sunxi.h>
#include <linux/sys_config.h>
#include <linux/ctype.h>
#include <linux/crypto.h>
#include <linux/err.h>
#include <linux/scatterlist.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#define LOG_TAG "misc_gpio:"
#define print_dbg(fmt, ...) printk(KERN_DEBUG LOG_TAG "%s:%d->" fmt, \
__func__, __LINE__, ##__VA_ARGS__)
#define print_err(fmt, ...) printk(KERN_ERR LOG_TAG "%s:%d->" fmt, \
__func__, __LINE__, ##__VA_ARGS__)
#define print_war(fmt, ...) printk(KERN_WARNING LOG_TAG "%s:%d->" fmt, \
__func__, __LINE__, ##__VA_ARGS__)
typedef struct {
int gpio;
int value;
}st_gpio_param;
#define GPIO_MAGIC 'G'
#define GET_GPIO_LEVEL _IOR(GPIO_MAGIC, 0, st_gpio_param*)
#define SET_GPIO_LEVEL _IOR(GPIO_MAGIC, 1, st_gpio_param*)
#define INPUT_GPIO 0
#define OUTPUT_GPIO 1
#define PIN_BANK_NAME_MAX_LEN 7
#define GPIO_USER_NAME_MAX_LEN 15
struct misc_gpio_config {
char pin_bank[PIN_BANK_NAME_MAX_LEN + 1];
char user[GPIO_USER_NAME_MAX_LEN + 1];
struct gpio_config config;
};
static int g_gpio_num = 0;
static struct misc_gpio_config* g_gpio_array = NULL;
struct gpio_config g_attr_gpio_config = {0, 0, 0, 0, 0};
static DEFINE_MUTEX(itech_gpio_mutex);
static int get_parameter(const char *buf, int *data, size_t size)
{
char *after;
size_t count;
int tmp;
tmp = simple_strtoul(buf, &after, 10);
count = after - buf;
if (isspace(*after))
count++;
if (count == size){
*data = tmp;
return size;
}
return -EINVAL;
}
static int find_attr_gpio(void)
{
int i = 0;
for(i = 0;i <= g_gpio_num;i++) {
if(g_gpio_array[i].config.gpio == g_attr_gpio_config.gpio) {
g_attr_gpio_config.mul_sel = g_gpio_array[i].config.mul_sel;
return g_gpio_array[i].config.gpio;
}
}
if(i > g_gpio_num) {
return -1;
}
return -1;
}
static ssize_t show_all_gpio(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int i = 0;
struct gpio_config* pconfig;
printk("%-6s%-8s%-16s%-6s%-4s%-6s%-6s%-5s\n",
"index", "pin", "user", "gpio", "sel", "pull", "level", "data");
for(i = 0;i <= g_gpio_num;i++) {
pconfig = &(g_gpio_array[i].config);
printk("%-6d%-8s%-16s%-6d%-4d%-6d%-6d%-5d\n",
i, g_gpio_array[i].pin_bank, g_gpio_array[i].user, pconfig->gpio,
pconfig->mul_sel, pconfig->pull, pconfig->drv_level, gpio_get_value(pconfig->gpio));
}
return count;
}
static ssize_t set_gpio_num(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
return get_parameter(buf, &g_attr_gpio_config.gpio, count);
}
static ssize_t set_gpio_value(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int gpio = -1;
get_parameter(buf, &g_attr_gpio_config.data, count);
if(g_attr_gpio_config.data != 0 && g_attr_gpio_config.data != 1) {
printk(KERN_ERR "gpio num(%d),the set value(%d) is invalid.\n",
g_attr_gpio_config.gpio, g_attr_gpio_config.data);
return count;
}
gpio = find_attr_gpio();
if(gpio < 0) {
printk(KERN_ERR "gpio num(%d) is invalid.\n", g_attr_gpio_config.gpio);
return count;
}
if(g_attr_gpio_config.mul_sel != OUTPUT_GPIO) {
printk(KERN_ERR "gpio num(%d) mul_sel(%d) is not output.",
g_attr_gpio_config.gpio, g_attr_gpio_config.mul_sel);
return count;
}
gpio_set_value(g_attr_gpio_config.gpio, g_attr_gpio_config.data);
return count;
}
static ssize_t show_gpio_value(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int gpio = -1;
int value = -1;
get_parameter(buf, &g_attr_gpio_config.gpio, count);
gpio = find_attr_gpio();
if(gpio < 0) {
printk(KERN_ERR "gpio num(%d) is invalid.\n", g_attr_gpio_config.gpio);
return count;
}
/*
if(g_attr_gpio_config.mul_sel != OUTPUT_GPIO) {
printk(KERN_ERR "gpio num(%d) mul_sel(%d) is not output.",
g_attr_gpio_config.gpio, g_attr_gpio_config.mul_sel);
return count;
}
*/
value = gpio_get_value(g_attr_gpio_config.gpio);
printk("gpio(%d) value is %d.", g_attr_gpio_config.gpio, value);
return count;
}
static DEVICE_ATTR(show_all_gpio, 0666, NULL, show_all_gpio);
static DEVICE_ATTR(set_gpio_num, 0666, NULL, set_gpio_num);
static DEVICE_ATTR(set_gpio_value, 0666, NULL, set_gpio_value);
static DEVICE_ATTR(show_gpio_value, 0666, NULL, show_gpio_value);
static int _open (struct inode *node, struct file *fp)
{
print_dbg("enter\n");
return 0;
}
static int _release (struct inode *node, struct file *fp)
{
print_dbg("enter\n");
return 0;
}
static bool checkGpioAccess(int gpio)
{
int i = 0;
for(i = 0; i <= g_gpio_num; i++)
{
if(g_gpio_array[i].config.gpio == gpio) {
return true;
}
}
return false;
}
static int set_gpio_level(int gpio, int value)
{
print_dbg("enter\n");
if(!checkGpioAccess(gpio)) {
print_err("gpio(%d) is not configured.Please configure it firstly.\n", gpio);
return -1;
}
print_dbg("gpio = %d value = %d\n", gpio, value);
gpio_set_value(gpio, value);
return 0;
}
static int get_gpio_level(int gpio)
{
print_dbg("enter\n");
if(!checkGpioAccess(gpio)) {
print_err("gpio(%d) is not configured.Please configure it firstly.\n", gpio);
return -1;
}
print_dbg("gpio = %d\n", gpio);
return gpio_get_value(gpio);
}
static long _ioctl (struct file *fp, unsigned int cmd, unsigned long args)
{
long ret = 0;
switch(cmd) {
case SET_GPIO_LEVEL:
{
st_gpio_param stGpioParam = {0,0};
if(copy_from_user(&stGpioParam, (st_gpio_param*)args, sizeof(st_gpio_param))) {
print_err("SET_GPIO_LEVEL copy_from_user fail.\n");
return -1;
}
ret = set_gpio_level(stGpioParam.gpio, stGpioParam.value);
}
break;
case GET_GPIO_LEVEL:
{
st_gpio_param stGpioParam = {0,0};
if(copy_from_user(&stGpioParam, (st_gpio_param*)args, sizeof(st_gpio_param))) {
print_err("SET_GPIO_LEVEL copy_from_user fail.\n");
return -1;
}
stGpioParam.value = get_gpio_level(stGpioParam.gpio);
ret = copy_to_user((void *)args, &stGpioParam, sizeof(st_gpio_param));
}
break;
default:
print_err("command is error.");
return -1;
}
return ret;
}
static struct attribute *misc_attributes[] = {
&dev_attr_show_all_gpio.attr,
&dev_attr_set_gpio_num.attr,
&dev_attr_set_gpio_value.attr,
&dev_attr_show_gpio_value.attr,
NULL,
};
static struct attribute_group misc_attribute_group = {
.name = "itech_gpio",
.attrs = misc_attributes,
};
struct file_operations itech_gpio_fops = {
.owner = THIS_MODULE,
.open = _open,
.release = _release,
.unlocked_ioctl = _ioctl,
};
static struct miscdevice itech_misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "itech_gpio",
.fops = &itech_gpio_fops,
};
int request_itech_misc_gpio(const char* pin_bank, const char* user)
{
int index = 0;
int pin_bank_len = 0;
int user_len = 0;
if(pin_bank == NULL || user == NULL) {
print_err("params are error.\n");
return -1;
}
pin_bank_len = strlen(pin_bank);
user_len = strlen(user);
if(pin_bank_len == 0 || pin_bank_len > PIN_BANK_NAME_MAX_LEN
|| user_len == 0 || user_len > GPIO_USER_NAME_MAX_LEN) {
print_err("params's value is invalid, string len is too short or too long.\n");
return -1;
}
mutex_lock(&itech_gpio_mutex);
for(index = 0; index <= g_gpio_num; index++) {
if(!strcmp(pin_bank, g_gpio_array[index].pin_bank)) break;
}
if(index > g_gpio_num) {
print_err("%s is not a gpio.Please config it firstly.\n", pin_bank);
goto fail;
}
if(g_gpio_array[index].user[0] != '\0'
&& (strlen(g_gpio_array[index].user) != strlen(user)
|| strcmp(g_gpio_array[index].user, user))) {
print_err("Pin %s has been used, Please change another pin.\n", pin_bank);
goto fail;
}
strcpy(g_gpio_array[index].user, user);
mutex_unlock(&itech_gpio_mutex);
return g_gpio_array[index].config.gpio;
fail:
mutex_unlock(&itech_gpio_mutex);
return -1;
}
EXPORT_SYMBOL_GPL(request_itech_misc_gpio);
int init_one_gpio(struct platform_device *pdev, char* name,
struct gpio_config* const pconfig) {
struct device_node *gpio_node = pdev->dev.of_node;
int ret = -1;
int gpio = -1;
if(name == NULL || pconfig == NULL) {
print_err("%s\n", "param is error.");
return -1;
}
mutex_lock(&itech_gpio_mutex);
gpio = of_get_named_gpio_flags(gpio_node, name, 0, (enum of_gpio_flags *)pconfig);
if(!gpio_is_valid(gpio)) {
print_err("get gpio %s failed\n", name);
goto fail;
}
ret = gpio_request(gpio, name);
if (ret < 0) {
print_err("can't request %s gpio %d\n", name, gpio);
goto fail;
}
if(pconfig->mul_sel == 0){
ret = gpio_direction_input(gpio);
if (ret < 0) {
print_err("can't request input direction %s gpio %d\n", name, gpio);
goto fail;
}
}else if(pconfig->mul_sel == 1){
ret = gpio_direction_output(gpio, pconfig->data);
if (ret < 0) {
print_err("can't request output direction %s gpio %d\n",
name, gpio);
goto fail;
}
} else {
print_war("misc-GPIO : %s config.mul_sel:%d is not a gpio.\n", name, pconfig->mul_sel);
}
print_dbg("MISC-GPIO : %s gpio = %d\n", name, gpio);
mutex_unlock(&itech_gpio_mutex);
return gpio;
fail:
mutex_unlock(&itech_gpio_mutex);
return -1;
}
EXPORT_SYMBOL_GPL(init_one_gpio);
static int misc_gpio_probe(struct platform_device *pdev) {
int ret = -1;
int gpio_nums = 0;
int index = 0;
const char *pins_string = NULL;
const char* p = NULL;
char temp[PIN_BANK_NAME_MAX_LEN + 1] = "\0";
struct gpio_config config;
struct device_node *np = pdev->dev.of_node;
print_dbg("enter\n");
if (of_property_read_u32(np, "gpio_nums", &gpio_nums)) {
print_err("%s.\n","get resourcce gpio_nums fail");
return -1;
}
g_gpio_num = gpio_nums;
print_dbg("g_gpio_num = %d\n", g_gpio_num);
g_gpio_array = devm_kzalloc(&pdev->dev, gpio_nums*sizeof(struct misc_gpio_config), GFP_KERNEL);
if (!g_gpio_array) {
print_err("no memory.");
return -ENOMEM;
}
if (of_property_read_string(np, "pin_banks", &pins_string)) {
print_err("get resourcce pin_banks fail.\n");
return -1;
}
print_dbg("pin_banks = %s\n", pins_string);
if(pins_string == NULL || strlen(pins_string) == 0) {
print_err("value of pin_banks is error.");
return -1;
}
p = pins_string;
for(index = 0; index <= gpio_nums; index++) {
if(sscanf(p, "%s", temp) <= 0) break;
print_dbg("index = %d name = %s", index, temp);
p += (strlen(temp) + 1);
init_one_gpio(pdev, temp, &config);
memcpy(g_gpio_array[index].pin_bank, temp, PIN_BANK_NAME_MAX_LEN);
g_gpio_array[index].pin_bank[PIN_BANK_NAME_MAX_LEN] = '\0';
g_gpio_array[index].user[0] = '\0';
memcpy(&g_gpio_array[index].config, &config, sizeof(struct gpio_config));
if(*(p-1) == '\0') break;
while(*p == ' ') p++;
}
// 1.注册字符设备
ret = misc_register(&itech_misc_dev);
if (ret) {
print_err("register driver as misc device error!\n");
return ret;
}
// 2.创建sys文件系统操作文件
ret = sysfs_create_group(&itech_misc_dev.this_device->kobj,
&misc_attribute_group);
if (ret) {
print_err("register sysfs create group failed!\n");
return ret;
}
return 0;
}
static int misc_gpio_remove(struct platform_device *pdev)
{
WARN_ON(0 != misc_deregister(&itech_misc_dev));
sysfs_remove_group(&(itech_misc_dev.this_device->kobj),
&misc_attribute_group);
return 0;
}
static const struct of_device_id of_misc_gpio_match[] = {
{ .compatible = "icetech, misc-gpio", },
{},
};
static struct platform_driver misc_gpio_driver = {
.probe = misc_gpio_probe,
.remove = misc_gpio_remove,
.driver = {
.name = "misc-gpio",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(of_misc_gpio_match),
},
};
static int __init module_misc_gpio_init(void)
{
print_dbg("enter\n");
return platform_driver_register(&misc_gpio_driver);
}
static void __exit module_misc_gpio_exit(void)
{
platform_driver_unregister(&misc_gpio_driver);
}
// module_platform_driver(misc_gpio_driver);
fs_initcall(module_misc_gpio_init);
module_exit(module_misc_gpio_exit);
MODULE_AUTHOR("binn.chen@icetech-china.com");
MODULE_DESCRIPTION("icetech misc device gpio config driver");
MODULE_LICENSE("GPL");