从零开始之驱动发开、linux驱动(二十三、platform总线之数据驱动分离一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/82946648

本节开始引入总线概念。

总线是一种虚拟的概念,不针对任何具体的外设,但是它可以比较好的管理外设。

总线对外设的管理从设备和驱动两个方面说明。

比如我们有3个led灯要控制,一种是向我们之前的那样在软件中写死。

但仔细分析原来的驱动可以发现,我们的代码基本是固定的。

同时如果想改成另一个io端口的话,改源代码中的数据换成另一组寄存器就可以。

为此为了实现通用的,软件逻辑稳定。

更改的只是控制那个硬件。

我们把软件逻辑和硬件分离开,这样每次要更改控制对象,或控制参数,只需要更改硬件参数即可。

本节说的platform总线是一种匹配总线,匹配原则是根据device和drver的名字,如果一直,则会调用在driver部分编写的一个probe函数。

具体做什么完全由probe决定。

platform总线只是提供这种机制,用来把设备(数据参数),驱动(逻辑)分离和匹配。

这节先写一个简单的程序,后面几节在这节的基础上慢慢的改进,优化。

先看一下和硬件相关的,我们这里叫device怎么写。

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/init.h>
#include <linux/platform_device.h>


/* 列举用到的资源 */
static struct resource led_resource[] = { 
    /* 用到的寄存器 */
    [0] = { 
        .start = 0xe0200240, 
        .end   = 0xe0200247, 
        .flags = IORESOURCE_MEM, 
    },  
    /* 要控制那个io口(这种已经不是资源了,而是参数,这里为了方便,我用中断进行传参而已) */    
    [1] = { 
        .start = 3,
        .end   = 3,
        .flags  = IORESOURCE_IRQ,
    },  
};

/* 卸载device时会用到 */
void led_dev_release(struct device *dev)
{
    /* 我们这里device部分没申请任何资源,
       所以暂时保留一个空函数,如果device用到了则卸载这个 */
}

/* 平台总线设备(关于这个结构体的细节和如何使用,我在最后面会给出我在其它博客分析的链接) */
static struct platform_device led_dev = {
    .name = "myled",
    .num_resources = ARRAY_SIZE(led_resource),
    .resource = led_resource,
    .id = -1,        
    .dev = {
        .release = led_dev_release,
    },
};




static int led_dev_init(void)
{
    platform_device_register(&led_dev);
    return 0;
}

static void led_dev_exit(void)
{
    platform_device_unregister(&led_dev);
}

module_init(led_dev_init);
module_exit(led_dev_exit);

MODULE_LICENSE("GPL");

这里要说明的是,资源通常是下面的几种,我们用那个io口已经不能称作资源了(它属于具体的寄存在了)

#define IORESOURCE_IO		0x00000100	/* PCI/ISA I/O ports */
#define IORESOURCE_MEM		0x00000200
#define IORESOURCE_REG		0x00000300	/* Register offsets */
#define IORESOURCE_IRQ		0x00000400
#define IORESOURCE_DMA		0x00000800
#define IORESOURCE_BUS		0x00001000

因为ARM中寄存器和内存是统一编址的,所以gpio用到的资源标志用MEM和REG都是可以的。

注:这里不能用I/O,这里的io特指 PCI/ISA

如果内核没配置支持的话,是不能使用IO的(看下面,没配置相关的默认结束end是0)

/*
 * This is the limit of PC card/PCI/ISA IO space, which is by default
 * 64K if we have PC card, PCI or ISA support.  Otherwise, default to
 * zero to prevent ISA/PCI drivers claiming IO space (and potentially
 * oopsing.)
 *
 * Only set this larger if you really need inb() et.al. to operate over
 * a larger address space.  Note that SOC_COMMON ioremaps each sockets
 * IO space area, and so inb() et.al. must be defined to operate as per
 * readb() et.al. on such platforms.
 */
#ifndef IO_SPACE_LIMIT
#if defined(CONFIG_PCMCIA_SOC_COMMON) || defined(CONFIG_PCMCIA_SOC_COMMON_MODULE)
#define IO_SPACE_LIMIT ((resource_size_t)0xffffffff)
#elif defined(CONFIG_PCI) || defined(CONFIG_ISA) || defined(CONFIG_PCCARD)
#define IO_SPACE_LIMIT ((resource_size_t)0xffff)
#else
#define IO_SPACE_LIMIT ((resource_size_t)0)
#endif



struct resource ioport_resource = {
	.name	= "PCI IO",
	.start	= 0,
	.end	= IO_SPACE_LIMIT,
	.flags	= IORESOURCE_IO,
};
EXPORT_SYMBOL(ioport_resource);

struct resource iomem_resource = {
	.name	= "PCI mem",
	.start	= 0,
	.end	= -1,
	.flags	= IORESOURCE_MEM,
};

这里看一下driver部分如何编写

include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/list.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <asm/uaccess.h>

struct gpio_reg {
    unsigned int gpio_con;
    unsigned int gpio_dat;
};


static struct gpio_reg *gpj0 = NULL;
static int pin;
static int major;
static struct class *led_class;
static struct device *led_dev;

static int led_open(struct inode *inode, struct file *file)
{
    gpj0->gpio_con &= ~(0xf << (pin*4));
    gpj0->gpio_con |= (0x1 << (pin*4));

    gpj0->gpio_dat |= (0x1 << pin);

    return 0;
}


static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
    char val ;
    int ret;

    ret = copy_from_user(&val, buf, 1);
    if(ret) {
        printk("copy_from_user fail\n`");
        return -EIO;
    }

    if('1' == val)
    {
        gpj0->gpio_dat &= ~(0x1 << pin);
    }
    else
    {
        gpj0->gpio_dat |= (0x1 << pin);
    }

    return 0;
}

static struct file_operations led_ops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .write = led_write,
};

static int led_probe(struct platform_device *pdev)
{
    struct resource *res = NULL;
    int error = 0;


    printk(KERN_INFO"led_probe \n");

    /* 1.申请资源 */
    res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    if(!res) {
        printk(KERN_ERR"platform_get_resource 1 fail\n");
        error =  -ENXIO;
        goto err_exit;
    }
    pin = res->start;

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if(!res) {
        printk(KERN_ERR"platform_get_resource 2 fail\n");
        error =  -ENXIO;
        goto err_exit;
    }

    gpj0 = ioremap(res->start, res->end - res->start + 1);
    if(!gpj0) {
        printk(KERN_ERR"ioremap fail\n");
        error = -ENOMEM;
        goto err_exit;
    }

   /* 2.注册字符设备驱动 */
    major = register_chrdev(0, "myled", &led_ops);
    if(major <= 0)
    {
        printk(KERN_ERR"register_chrdev fail \n");
        error = -ENODEV;
        goto err_register_chrdev;
    }

     /* 创建一个类 */
    led_class = class_create(THIS_MODULE, "leds_class");
    if(!led_class)    {
        printk("class_create leds fail\n");
        error = -ENODEV;
        goto err_class_create;
    }

    /* 创建从属这个类的设备 */
    led_dev = device_create(led_class,NULL,MKDEV(major, 0), NULL, "led");
    if(!led_dev)    {
        error = -ENODEV;
        goto err_device_create_led;
    }


err_device_create_led:
    class_destroy(led_class);

err_class_create:
    unregister_chrdev(major, "myled");

err_register_chrdev:
    iounmap(gpj0);
err_exit:

    return error;

}

static int led_remove(struct platform_device *pdev)
{
    device_destroy(led_class, MKDEV(major, 0));
    class_destroy(led_class);
    unregister_chrdev(major, "myled");
    iounmap(gpj0);

    return 0;
}


/* (关于这个结构体的细节和如何使用,我在最后面会给出我在其它博客分析的链接) */
static struct platform_driver led_drv = {
    .probe = led_probe,
    .remove = led_remove,
    .driver = {
        .name = "myled",
    },
};


static int led_drv_init(void)
{
    platform_driver_register(&led_drv);
    return 0;
}

static void led_drv_exit(void)
{
    platform_driver_unregister(&led_drv);
}

module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");

分析driver部分的代码,可以发现,probe里面做的事,完全和我们之前的led驱动里面的一样。只是增加了platform机制用来分离数据参数。

关于platform我在下面博客中从多个层面进行过分析,里面连接的博客从不同角度(使用,细节,总选实现)进行了分析,这里就不重复造轮子了。

https://blog.csdn.net/qq_16777851/article/details/81350037

最后看一下,应用测试程序。和最初前两节的一样。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>



int main(int argc,char *argv[])
{

    if(argc != 3)
    {   
        printf("please input ./xxx /dev/ledx on|off");
        return -1; 
    }   
    
    int fd = open(argv[1], O_RDWR);

    if(fd < 0)
    {   
        printf("open %s fail\n",argv[1]);
        return -1; 
    }   
    
    write(fd, argv[2], 5); 
    printf("\n");

    return 0;
}

后面再写几节关于platform的,对这个驱动进行全方面的优化。

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/82946648
今日推荐