我们再谈一下平台总线。
前面讲平台总线是可以让drv和dev实现分离。主要作用是用于平台升级。
现在结合设备树来看一下 平台总线的好处。(平台总线是虚拟总线,用于一些ADC,PWM这样的没有实体总线连接(iIC spi…)的一些设备,为了管理这些设备统一虚拟出平台总线来管理)
1)首先我们要了解到
当我们在设备树文件上写了应该设备后(属于我们的直接定义的设备,可以有2^20个子设备(前面说可以放无限个是错误的)
它就会把设备树的信息放在/sys/bus/plaoform/device下(那我要是iic设备就会放在/sys/bus/i2c下)
2)这样我们只要写DRV程序就可以了,利用platfrom根据compatinle进行匹配,然后用
platform_get_resource拿到硬件信息;
platform_get_irq拿到中断号
这样就可以在驱动程序中拿到硬件资源了,并且我们只需要写驱动程序,而硬件信息只需要修改设备树就可以了。
就不用像以前一样,还要写dev写drv写bus。。。这么麻烦。
———————————————————————————————————————
申请设备号的三种方式:
1)
register_chrdev(unsigned int major, const char * name, const struct file_operations * fops)
在 2.4 的内核我们使用 register_chrdev(0, “hello”, &hello_fops) 来进行字符设备设备节点的分配,这种方式每一个主设备号只能存放一种设备,它们使用相同的 file_operation 结构体,也就是说内核最多支持 256 个字符设备驱动程序。
- register_chrdev_region(dev, 1, FSPWM_DEV_NAME);
cdev_init(&fspwm->cdev, &fspwm_ops);
cdev_add(&fspwm->cdev, dev, 1);
register_chrdev_region() 函数用于分配指定的设备编号范围。它需要手动初始化cdev并且将cdev加入到内核的cdev链表中。
3)
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
alloc_chrdev_region() 函数用于动态申请设备编号范围。
——————————————————————————————————————
创建设备节点的两种方式。
对设备进行操作,其实就是对cdev进行一些操作。cdev里面有主设备号以及file_operatorion.
那么创建设备节点就可以便于用户找到主设备号并进行file_operatorion.操作。
1)手动创建:利用命令 :mknod /dev/(你自定义设备的名字) c(字符设备)/d(块设备) 主设备号(一定要和你申请的一样)
2)自动创建:对于热插拔事件,我们应用程序要完成,插入加载模块,并且自动创建设备节点,拔自动卸载模块,和注销设备节点。
对于普通事件我们也一般采用自动创建的方式。
:class_create(THIS_MODULE,“xxx”);
:device_create(…);
————————————————————————————————————————
我们还是讲怎么ADC驱动吧!!!!
平台总线驱动的另一种模板:
我们之前的都是module_init module_exit。。。实例化应该drv,设置匹配的complation 在init里面把drv注册进去,在exit里面移除。
。。。。千篇一律。。
那么我们在内核中,把这种固定的用平台总线的驱动封装了一个函数
module_platform_driver(fspwm_drv);
然后我们就可以这样做:
tatic const struct of_device_id fspwm_of_matches[] = {
{ .compatible = "fs4412,fspwm", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fspwm_of_matches);
struct platform_driver fspwm_drv = {
.driver = {
.name = "fspwm",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(fspwm_of_matches),
},
.probe = fspwm_probe,
.remove = fspwm_remove,
};
module_platform_driver(fspwm_drv);
其实module_platform_driver(fspwm_drv);里面也调用了init 和exit就是进行了封装而已。
——————————————————————————————————————
我们还是讲ADC驱动吧。。。这有什么讲的?我们模型都有了,驱动不是照套吗?
1)写设备树。
2)按上面的编写驱动代码模型。匹配成功之后调用proble。
3)在proble里面申请设备号,创建设备节点,获取设备信息。地址映射。
这样我们就拿到硬件信息了,还有虚拟地址。
那么不管他是ADC,PWM,KEY(当然key是用input子系统好一点),Led…都是对一些地址进行一些读写操作。
那我们来看一下对ADC需要哪些操作?
我们在proble下面肯定要对一些寄存器进行初始化:
writel((1 << 16) | (1 << 14) | (19 << 6), fsadc->adccon);
1<< 16是设置精度为12;
1<<14使能分频
19<<6设置为20倍分频。
初始化好了,之后我们就该操作了。我们要的是把继电器中连续变化的模拟量转化成数字量


可以看到我们的电位器连接到ain3口。所以我们要选择adcmux为0011
我们去读这个ADCDAT的0~11位就可以读到结果,
当我们数据转化的时候就会触发中断去读ADCDAT的数据,然后读取完毕,就会利用clrint清除中断。
————————————————详情请看arm——ADC裸机开发
驱动代码:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/interrupt.h>
#include "fsadc.h"
#define FSADC_MAJOR 256
#define FSADC_MINOR 6
#define FSADC_DEV_NAME "fsadc"
struct fsadc_dev {
unsigned int __iomem *adccon;
unsigned int __iomem *adcdat;
unsigned int __iomem *clrint;
unsigned int __iomem *adcmux;
unsigned int adcval;
struct completion completion;
atomic_t available;
unsigned int irq;
struct device *dev;
struct class *cls;
struct cdev cdev;
};
static int fsadc_open(struct inode *inode, struct file *filp)
{
struct fsadc_dev *fsadc = container_of(inode->i_cdev, struct fsadc_dev, cdev);
filp->private_data = fsadc;
if (atomic_dec_and_test(&fsadc->available))
return 0;
else {
atomic_inc(&fsadc->available);
return -EBUSY;
}
}
static int fsadc_release(struct inode *inode, struct file *filp)
{
struct fsadc_dev *fsadc = filp->private_data;
atomic_inc(&fsadc->available);
return 0;
}
static long fsadc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct fsadc_dev *fsadc = filp->private_data;
union chan_val cv;
if (_IOC_TYPE(cmd) != FSADC_MAGIC)
return -ENOTTY;
switch (cmd) {
case FSADC_GET_VAL:
if (copy_from_user(&cv, (union chan_val __user *)arg, sizeof(union chan_val)))
return -EFAULT;
if (cv.chan > AIN3)
return -ENOTTY;
writel(cv.chan, fsadc->adcmux);
writel(readl(fsadc->adccon) | 1, fsadc->adccon);
if (wait_for_completion_interruptible(&fsadc->completion))
return -ERESTARTSYS;
cv.val = fsadc->adcval & 0xFFF;
if (copy_to_user( (union chan_val __user *)arg, &cv, sizeof(union chan_val)))
return -EFAULT;
break;
default:
return -ENOTTY;
}
return 0;
}
static irqreturn_t fsadc_isr(int irq, void *dev_id)
{
struct fsadc_dev *fsadc = dev_id;
fsadc->adcval = readl(fsadc->adcdat);
writel(1, fsadc->clrint);
complete(&fsadc->completion);
return IRQ_HANDLED;
}
static struct file_operations fsadc_ops = {
.owner = THIS_MODULE,
.open = fsadc_open,
.release = fsadc_release,
.unlocked_ioctl = fsadc_ioctl,
};
static int fsadc_probe(struct platform_device *pdev)
{
printk("--------%s------\n", __FUNCTION__);
int ret;
dev_t dev;
struct fsadc_dev *fsadc;
struct resource *res;
dev = MKDEV(FSADC_MAJOR, FSADC_MINOR);
ret = register_chrdev_region(dev, 1, FSADC_DEV_NAME); //分配指定设备编号
if (ret)
goto reg_err;
fsadc = kzalloc(sizeof(struct fsadc_dev), GFP_KERNEL); //实例化设备信息结构体
if (!fsadc) {
ret = -ENOMEM;
goto mem_err;
}
platform_set_drvdata(pdev, fsadc); //把匹配成功之后的pdev数据拿到fsadc结构体中
cdev_init(&fsadc->cdev, &fsadc_ops);
fsadc->cdev.owner = THIS_MODULE;
ret = cdev_add(&fsadc->cdev, dev, 1);
if (ret)
goto add_err;
fsadc->cls = class_create(THIS_MODULE,"adc_cls");
fsadc->dev = device_create(fsadc->cls, NULL, dev,NULL,"fs_adc");
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //从平台总线获取匹配成功的信息里面的memory信息的第0个信息
if (!res) {
ret = -ENOENT;
goto res_err;
}
fsadc->adccon = ioremap(res->start, resource_size(res)); //映射
if (!fsadc->adccon) {
ret = -EBUSY;
goto map_err;
}
fsadc->adcdat = fsadc->adccon + 3;
fsadc->clrint = fsadc->adccon + 6;
fsadc->adcmux = fsadc->adccon + 7;
fsadc->irq = platform_get_irq(pdev, 0); //你添加了设备树之后,你的设备树信息,会出现在/sys/bus/plaoform/
///sys/bus/plaoform/device/adc@1......根据compatible匹配成功
//就可以获取到信息,中断继承,可以拿到中断号
if (fsadc->irq < 0) {
ret = fsadc->irq;
goto irq_err;
}
ret = request_irq(fsadc->irq, fsadc_isr, 0, "adc", fsadc); //申请中断
if (ret)
goto irq_err;
writel((1 << 16) | (1 << 14) | (19 << 6), fsadc->adccon);
init_completion(&fsadc->completion);
atomic_set(&fsadc->available, 1);
return 0;
irq_err:
iounmap(fsadc->adccon);
map_err:
res_err:
cdev_del(&fsadc->cdev);
add_err:
kfree(fsadc);
mem_err:
unregister_chrdev_region(dev, 1);
reg_err:
return ret;
}
static int fsadc_remove(struct platform_device *pdev)
{
dev_t dev;
struct fsadc_dev *fsadc = platform_get_drvdata(pdev);
dev = MKDEV(FSADC_MAJOR, FSADC_MINOR);
writel((readl(fsadc->adccon) & ~(1 << 16)) | (1 << 2), fsadc->adccon);
free_irq(fsadc->irq, fsadc);
iounmap(fsadc->adccon);
cdev_del(&fsadc->cdev);
kfree(fsadc);
unregister_chrdev_region(dev, 1);
return 0;
}
static const struct of_device_id fsadc_of_matches[] = {
{ .compatible = "fs4412,fsadc", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fsadc_of_matches);
struct platform_driver fsadc_drv = {
.driver = {
.name = "fsadc",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(fsadc_of_matches),
},
.probe = fsadc_probe,
.remove = fsadc_remove,
};
module_platform_driver(fsadc_drv);
MODULE_LICENSE("GPL");
应用代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include "fsadc.h"
int main(int argc, char *argv[])
{
int fd;
int ret;
union chan_val cv;
fd = open("/dev/fs_adc", O_RDWR);
if (fd == -1)
goto fail;
while (1) {
cv.chan = 3;
ret = ioctl(fd, FSADC_GET_VAL, &cv);
if (ret == -1)
goto fail;
printf("current volatage is: %.2fV\n", 1.8 * cv.val / 4095.0);
sleep(1);
}
fail:
perror("adc test");
exit(EXIT_FAILURE);
}

本文介绍了STM32平台驱动ADC的过程,包括设备树在平台驱动中的作用、如何通过平台总线获取硬件资源和中断号,以及字符设备的注册和设备节点的创建。文章还讨论了平台总线驱动的模板和ADC驱动的寄存器初始化,强调了ADC转换过程中的中断处理和数据读取。

被折叠的 条评论
为什么被折叠?



