前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux笔记(21)| platform总线驱动分析

Linux笔记(21)| platform总线驱动分析

作者头像
飞哥
发布2021-02-02 10:56:05
2.3K0
发布2021-02-02 10:56:05
举报

//本文主要参考《野火Linux实战开发指南》

上次跟大家分享了设备模型的一些东西,包括总线、设备、驱动等的一些概念,还有他们之间的联系。今天要分享的是platform总线驱动,platform总线是总线的一种,这是相对于物理总线来说的,这是一种虚拟的总线。

为什么要有platform总线呢?因为在Linux当中,对于I2C、SPI、USB这些常见类型的物理总线来说,Linux内核会自动创建与之相应的驱动总线,因此I2C设备、SPI设备、 USB设备自然是注册挂载在相应的总线上。但是,实际项目开发中还有很多结构简单的设备,对它们进行控制并不需要特殊的时序。它们也就没有相应的物理总线,比如led、rtc时钟、蜂鸣器、按键等等,Linux内核将不会为它们创建相应的驱动总线。

为了使这部分设备的驱动开发也能够遵循设备驱动模型,Linux内核引入了一种虚拟的总线——平台总线(platform bus)。平台总线用于管理、挂载那些没有相应物理总线的设备,这些设备被称为平台设备,对应的设备驱动则被称为平台驱动。

平台总线也是基于上一节当中的设备模型,在上一节里,介绍了创建总线,也提到了在实际当中,并不需要我们去创建新的总线,而是一般直接用现成的,平台总线就是这样一种现成的总线。这也意味着我们不需要去实现总线里的各种函数,包括match函数这种,可以直接不用管这部分。

今天的重点就是和大家分享一下我是如何分析在平台总线下led的设备文件和驱动文件是怎么写的。

先来看设备文件。

代码语言:javascript
复制
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#define CCM_CCGR1                               0x20C406C  //时钟控制寄存器
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04         0x20E006C  //GPIO1_04复用功能选择寄存器
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04         0x20E02F8  //PAD属性设置寄存器
#define GPIO1_GDIR                               0x0209C004  //GPIO方向设置寄存器(输入或输出)
#define GPIO1_DR                                 0x0209C000  //GPIO输出状态寄存器

#define CCM_CCGR3                               0x020C4074
#define GPIO4_GDIR                               0x020A8004
#define GPIO4_DR                                 0x020A8000

#define IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO020       0x020E01E0
#define IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO020       0x020E046C

#define IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO019       0x020E01DC
#define IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO019       0x020E0468

static struct resource rled_resource[] = {
  [0] = DEFINE_RES_MEM(GPIO1_DR, 4),
  [1] = DEFINE_RES_MEM(GPIO1_GDIR, 4),
  [2] = DEFINE_RES_MEM(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04, 4),
  [3] = DEFINE_RES_MEM(CCM_CCGR1, 4),
  [4] = DEFINE_RES_MEM(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04, 4),
};

static struct resource gled_resource[] = {
  [0] = DEFINE_RES_MEM(GPIO4_DR, 4),
  [1] = DEFINE_RES_MEM(GPIO4_GDIR, 4),
  [2] = DEFINE_RES_MEM(IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO020, 4),
  [3] = DEFINE_RES_MEM(CCM_CCGR3, 4),
  [4] = DEFINE_RES_MEM(IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO020, 4),
};

static struct resource bled_resource[] = {
  [0] = DEFINE_RES_MEM(GPIO4_DR, 4),
  [1] = DEFINE_RES_MEM(GPIO4_GDIR, 4),
  [2] = DEFINE_RES_MEM(IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO019, 4),
  [3] = DEFINE_RES_MEM(CCM_CCGR3, 4),
  [4] = DEFINE_RES_MEM(IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO019, 4),
};
/* not used */ 
static void led_release(struct device *dev)
{

}

/* led hardware information */
unsigned int rled_hwinfo[2] = { 4, 26 };
unsigned int gled_hwinfo[2] = { 20, 12 };
unsigned int bled_hwinfo[2] = { 19, 12 };

/* red led device */ 
static struct platform_device rled_pdev = {
  .name = "led_pdev",
  .id = 0,
  .num_resources = ARRAY_SIZE(rled_resource),
  .resource = rled_resource,
  .dev = {
    .release = led_release,
    .platform_data = rled_hwinfo,
    },
};
/* green led device */ 
static struct platform_device gled_pdev = {
  .name = "led_pdev",
  .id = 1,
  .num_resources = ARRAY_SIZE(gled_resource),
  .resource = gled_resource,
  .dev = {
    .release = led_release,
    .platform_data = gled_hwinfo,
    },
};
/* blue led device */ 
static struct platform_device bled_pdev = {
  .name = "led_pdev",
  .id = 2,
  .num_resources = ARRAY_SIZE(bled_resource),
  .resource = bled_resource,
  .dev = {
    .release = led_release,
    .platform_data = bled_hwinfo,
    },
};

static __init int led_pdev_init(void)
{
  printk("pdev init\n");
  platform_device_register(&rled_pdev);
  platform_device_register(&gled_pdev);
  platform_device_register(&bled_pdev);
  return 0;
}

module_init(led_pdev_init);

static __exit void led_pdev_exit(void)
{
  printk("pdev exit\n");
  platform_device_unregister(&rled_pdev);
  platform_device_unregister(&gled_pdev);
  platform_device_unregister(&bled_pdev);
}

module_exit(led_pdev_exit);

MODULE_AUTHOR("embedfire");
MODULE_LICENSE("GPL");

对于编译成模块的文件,先从init部分开始看,可以发现,就做了一件事情——向内核注册一个平台设备。

代码语言:javascript
复制
int platform_device_register(struct platform_device *pdev)

所以,其实非常容易,只要创建一个platform_device类型的对象,然后对对象进行初始化,把它作为参数放进去就行了(这其实是面向对象的表达方式,所谓“对象”就是变量)。所以,我们要做的无非就是如何来对对象初始化(说白了就是如何填充结构体)。

那我们得先看一下这个结构体长什么样子。这里我把结构体的包含关系给画出来了:

我们不需要对每个成员进行填充,只要对几个重要的成员进行填充就行了。我们来看一下代码里相应的部分:

name就是设备的名字,这个要和驱动里面一致,因为总线是通过名字来匹配的。

id是相同类型设备的区分,比如都是led设备,名字可以一样,只要id不同就行。

resource是平台设备提供给驱动的资源。通常用一个数组来存放,上面的num_resource就是数组元素的个数。

那么这个resource到底是什么东西呢?在Linux里面用了几个宏来定义资源

在嵌入式中,基本上没有IO地址空间,所以通常使用IORESOURCE_MEM。代码中是这样定义的。

我们把宏一层层展开,就得到下面这样子:

可以看到,IORESOURCE_MEM这个宏最终是帮我们填充了resource结构体。可以看到,是把所有相关的寄存器的数据给放进去了。具体是怎么填充的,大家只要像我上面那样把宏给一层层展开就知道了。

最后再说一下.dev成员里面的.platform_data成员。platform_data是用来保存设备的私有数据的,platform_data是void *类型的万能指针,无论你想要提供的是什么内容,只需要把数据的地址赋值给platform_data即可。

在这里,我们是给platform_data传了一个数组名(数组名就是int *,可以用void*来接收),数组里存放了两个数据,第一个数据是led的引脚号,第二个是引脚对应的时钟寄存器偏移量。

这些数据待会儿都会通过总线传给驱动。

到这里,也就把所有内容都分析完了,可以看到,虽然看起来很复杂,但是其实做的事情不多,就是定义一个platform device类型的对象,对对象进行初始化(填充数据),然后调用register函数注册即可。

设备文件主要就是提供硬件资源信息,这和上一节里讲的内容完全是一致的。

接下来看一下驱动文件是如何写的:

代码语言:javascript
复制
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/fs.h>

#define DEV_MAJOR 243
#define DEV_NAME  "led"

static struct class *my_led_class;

struct led_data {
  unsigned int led_pin;
  unsigned int clk_regshift;

  unsigned int __iomem *va_dr;
  unsigned int __iomem *va_gdir;
  unsigned int __iomem *va_iomuxc_mux;
  unsigned int __iomem *va_ccm_ccgrx;
  unsigned int __iomem *va_iomux_pad;  

  struct cdev led_cdev;

};

static int led_cdev_open(struct inode *inode, struct file *filp)
{
  printk("%s\n", __func__);
  
  struct led_data *cur_led = container_of(inode->i_cdev, struct led_data, led_cdev);
  unsigned int val = 0;

  val = readl(cur_led->va_ccm_ccgrx);
  val &= ~(3 << cur_led->clk_regshift);
  val |= (3 << cur_led->clk_regshift);
  writel(val, cur_led->va_ccm_ccgrx);

  writel(5, cur_led->va_iomuxc_mux);

  writel(0x1F838, cur_led->va_iomux_pad);

  val = readl(cur_led->va_gdir);
  val &= ~(1 << cur_led->led_pin);
  val |= (1 << cur_led->led_pin);
  writel(val, cur_led->va_gdir);

  val = readl(cur_led->va_dr);
  val |= (0x01 << cur_led->led_pin);
  writel(val, cur_led->va_dr);

  filp->private_data = cur_led;

  return 0;
}


static int led_cdev_release(struct inode *inode, struct file *filp)
{
  return 0;
}

static ssize_t led_cdev_write(struct file *filp, const char __user * buf,
            size_t count, loff_t * ppos)
{
  unsigned long val = 0;
  unsigned long ret = 0;

  int tmp = count;

  struct led_data *cur_led = (struct led_data *)filp->private_data;

  kstrtoul_from_user(buf, tmp, 10, &ret);

  val = readl(cur_led->va_dr);
  if (ret == 0)
    val &= ~(0x01 << cur_led->led_pin);
  else
    val |= (0x01 << cur_led->led_pin);

  writel(val, cur_led->va_dr);
  *ppos += tmp;

  return tmp;
}

static struct file_operations led_cdev_fops = {
  .open = led_cdev_open,
  .release = led_cdev_release,
  .write = led_cdev_write,
};



static int led_pdrv_probe(struct platform_device *pdev)
{
  struct led_data *cur_led;
  unsigned int *led_hwinfo;
  
  struct resource *mem_dr;
  struct resource *mem_gdir;
  struct resource *mem_iomuxc_mux;
  struct resource *mem_ccm_ccgrx;
  struct resource *mem_iomux_pad;   

  dev_t cur_dev;

  int ret = 0;
  
  printk("led platform driver probe\n");

  cur_led = devm_kzalloc(&pdev->dev, sizeof(struct led_data), GFP_KERNEL);
  if(!cur_led)
    return -ENOMEM;
  led_hwinfo = devm_kzalloc(&pdev->dev, sizeof(unsigned int)*2, GFP_KERNEL);
  if(!led_hwinfo)
    return -ENOMEM;

  /* get the pin for led and the reg's shift */
  led_hwinfo = dev_get_platdata(&pdev->dev);

  cur_led->led_pin = led_hwinfo[0];
  cur_led->clk_regshift = led_hwinfo[1];
  /* get platform resource */
  mem_dr = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  mem_gdir = platform_get_resource(pdev, IORESOURCE_MEM, 1);
  mem_iomuxc_mux = platform_get_resource(pdev, IORESOURCE_MEM, 2);
  mem_ccm_ccgrx = platform_get_resource(pdev, IORESOURCE_MEM, 3);
  mem_iomux_pad = platform_get_resource(pdev, IORESOURCE_MEM, 4);

  cur_led->va_dr =
      devm_ioremap(&pdev->dev, mem_dr->start, resource_size(mem_dr));
  cur_led->va_gdir =
      devm_ioremap(&pdev->dev, mem_gdir->start, resource_size(mem_gdir));
  cur_led->va_iomuxc_mux =
      devm_ioremap(&pdev->dev, mem_iomuxc_mux->start,
       resource_size(mem_iomuxc_mux));
  cur_led->va_ccm_ccgrx =
      devm_ioremap(&pdev->dev, mem_ccm_ccgrx->start,
       resource_size(mem_ccm_ccgrx));
  cur_led->va_iomux_pad =
      devm_ioremap(&pdev->dev, mem_iomux_pad->start,
       resource_size(mem_iomux_pad));

  cur_dev = MKDEV(DEV_MAJOR, pdev->id);

  register_chrdev_region(cur_dev, 1, "led_cdev");

  cdev_init(&cur_led->led_cdev, &led_cdev_fops);

  ret = cdev_add(&cur_led->led_cdev, cur_dev, 1);
  if(ret < 0)
  {
    printk("fail to add cdev\n");
    goto add_err;
  }
  
  device_create(my_led_class, NULL, cur_dev, NULL, DEV_NAME "%d", pdev->id);

  /* save as drvdata */ 
  platform_set_drvdata(pdev, cur_led);

  return 0;

add_err:
  unregister_chrdev_region(cur_dev, 1);
  return ret;
}


static int led_pdrv_remove(struct platform_device *pdev)
{
  dev_t cur_dev; 
  struct led_data *cur_data = platform_get_drvdata(pdev);


  printk("led platform driver remove\n");

  cur_dev = MKDEV(DEV_MAJOR, pdev->id);

  cdev_del(&cur_data->led_cdev);

  device_destroy(my_led_class, cur_dev);

  unregister_chrdev_region(cur_dev, 1);

  return 0;
}

static struct platform_device_id led_pdev_ids[] = {
  {.name = "led_pdev"},
  {}
};

MODULE_DEVICE_TABLE(platform, led_pdev_ids);

static struct platform_driver led_pdrv = {
  
  .probe = led_pdrv_probe,
  .remove = led_pdrv_remove,
  .driver.name = "led_pdev",
  .id_table = led_pdev_ids,
};



static __init int led_pdrv_init(void)
{
  printk("led platform driver init\n");

  my_led_class = class_create(THIS_MODULE, "my_leds");
  
  platform_driver_register(&led_pdrv);

  return 0;
}
module_init(led_pdrv_init);


static __exit void led_pdrv_exit(void)
{
  printk("led platform driver exit\n");  

  platform_driver_unregister(&led_pdrv);

  class_destroy(my_led_class);

}
module_exit(led_pdrv_exit);

MODULE_AUTHOR("Embedfire");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("the example for platform driver");

驱动文件稍微复杂一些,但是总结一下其实也并不难。可以发现,不管是设备文件也好,驱动文件也好,核心都是调用register函数来注册,其他的工作都是由这个引申出来的。

来看一下函数原型:

代码语言:javascript
复制
#define platform_driver_register(drv) \
  __platform_driver_register(drv, THIS_MODULE)
代码语言:javascript
复制
int __platform_driver_register(struct platform_driver *drv,
        struct module *owner)

所以我们只要创建一个platform_driver类型的对象,并且进行填充就行了。

这里我也把结构体的包含关系给大家画出来:

可以看到,我们主要是要实现probe函数和remove函数,另外要对device_driver结构体进行填充,还有一个platform_device_id。

这里解释一下为什么有两个name,platform总线提供了四种匹配方式,并且这四种方式存在着优先级:设备树机制>ACPI匹配模式>id_table方式>字符串比较。也就是说总线会先比较id_table里的名字,然后再比较.driver.name里的名字。实际上我们只要提供一种就行了。

重点还是在probe函数里面,上一节也说过,probe函数是当驱动和设备相匹配的时候会自动执行的。搞清楚了这个怎么写,驱动也就基本结束了。

probe函数里要做什么事情呢?总结如下:

1、获取平台资源,对资源进行处理

先从平台获得资源,这个资源就是寄存器信息,然后对寄存器进行地址映射。

代码语言:javascript
复制
struct resource *platform_get_resource(struct platform_device *dev,
               unsigned int type, unsigned int num)          
  • dev: 指定要获取哪个平台设备的资源;
  • type: 指定获取资源的类型,如IORESOURCE_MEM、IORESOURCE_IO等;
  • num: 指定要获取的资源编号。每个设备所需要资源的个数是不一定的,为此内核对这些资源进行了编号,对于不同的资源,编号之间是相互独立的。
代码语言:javascript
复制
void __iomem *devm_ioremap(struct device *dev, resource_size_t offset,
         resource_size_t size)

devm_ioremap将获取到的寄存器地址转化为虚拟地址。

  • dev: 指定要获取哪个平台设备的资源;
  • offset: 寄存器的物理地址
  • size: 资源大小,这里就是寄存器4个字节
代码语言:javascript
复制
static inline void *dev_get_platdata(const struct device *dev)

这个是获取设备的私有数据,在上面也说过了,里面保存了led 的引脚编号和时钟寄存器的偏移量。

2、注册字符设备

使用MKDEV宏定义来创建一个设备编号,再调用register_chrdev_region、cdev_init、cdev_add等函数来注册字符设备。使用platform_set_drvdata函数,将LED数据信息存入在平台驱动结构体中pdev->dev->driver_data中。

这些函数没有太多要注意的,只要根据函数的参数类型往里面放数据就行了,而数据在刚刚的第一步里面都已经得到了。

只有cdev_init这个函数要特别注意一下,因为这里涉及到了file_operation结构体的填充。其实主要就是open和write函数接口的编写。实际上,这部分才是真正操控硬件的部分,从实质上来说是最核心的部分,其他的只是一些“壳子”而已。

我们只要把open和write函数搞定了,驱动也就搞定了。其实也不难。

open里面主要是对硬件进行初始化,这和裸机里面基本是一样的,比如开时钟,配置引脚模式等等,唯一的不同就是,在裸机里面我们是直接操作寄存器,使用的是寄存器的物理地址,而在这里,我们并没有直接操作寄存器,而是操作从平台设备获取的“资源”,这些资源本质上还是寄存器信息,在第一步里面获取到的,并且进行了地址映射。

write是对寄存器进行“写入”,从用户空间获得数据,然后写入寄存器。

代码语言:javascript
复制
int __must_check kstrtoul_from_user(const char __user *s, size_t count, 
                                    unsigned int base, unsigned long *res);

从逻辑上来说和裸机是一样的,不同的就是数据来源不一样。

这样,我们就把驱动都分析完了,最后编写Makefile来编译程序,生产.ko文件,安装模块,就可以在/dev目录下看到注册的led设备文件,往设备文件里进行读写就可以操控硬件了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-01-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 电子技术研习社 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档