转载地址:https://blog.youkuaiyun.com/lizuobin2/article/details/54563985
开发板:tiny4412SDK + S702 + 4GB Flash
要移植的内核版本:Linux-4.4.0 (支持device tree)
u-boot版本:友善之臂自带的 U-Boot 2010.12
busybox版本:busybox 1.25
目标:
在第一篇文章中,学习了在设备树中增加GPIO资源,在代码中转为对应的中断,本文目标学习在设备树中直接使用中断资源,实现ADC采集底板上滑动变阻器的电压。
原理图:
设备树参考:
adc: adc@126C0000 {
compatible = "samsung,exynos-adc-v1";
reg = <0x126C0000 0x100>;
interrupt-parent = <&combiner>;
interrupts = <10 3>;
clocks = <&clock CLK_TSADC>;
clock-names = "adc";
#io-channel-cells = <1>;
io-channel-ranges;
samsung,syscon-phandle = <&pmu_system_controller>;
status = "disabled";
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
interrupt combiner 见芯片手册第10章,Interrupt combiner combines several interrupt sources as a group. Several interrupt requests in a group make a group interrupt request and a single request signal。一组中断源公用一个中断请求信号,2440也有类似例子。以ADC为例,它位于组INITG10的number3
samsung,exynos4210-combiner.txt
Required properties:
- compatible: should be "samsung,exynos4210-combiner".
- interrupt-controller: Identifies the node as an interrupt controller.
- #interrupt-cells: should be <2>. The meaning of the cells are
* First Cell: Combiner Group Number.
* Second Cell: Interrupt number within the group.
也就是:
interrupt-parent = <&combiner>;
interrupts = <10 3>;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
设备树:
adc_demo@126C0000{
compatible = "tiny4412,adc_demo";
reg = <0x126C 0x20>;
clocks = <&clock CLK_TSADC>;
clock-names = "timers";
interrupt-parent = <&combiner>;
interrupts = <10 3>;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
在代码中,我们仍可以向平台设备一样,使用
参考:Exynos_adc.c (drivers\iio\adc) 19412 2016/12/17
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "no irq resource?\n");
return irq;
}
...
ret = request_irq(info->irq, exynos_adc_isr, 0, dev_name(&pdev->dev), info);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
代码:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/export.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/time.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/interrupt.h>
DECLARE_WAIT_QUEUE_HEAD(wait);
static int major;
static struct cdev adc_cdev;
static struct class *cls;
struct ADC_BASE
{
unsigned int ADCCON; //0
unsigned int temp0; //
unsigned int ADCDLY; //8
unsigned int ADCDAT; //c
unsigned int temp1; //10
unsigned int temp2; //14
unsigned int CLRINTADC; //18
unsigned int ADCMUX; //1c
};
volatile static struct ADC_BASE *adc_base = NULL;
static int adc_open(struct inode *inode, struct file *file)
{
printk("adc_open\n");
return 0;
}
static int adc_release(struct inode *inode, struct file *file)
{
printk("adc_exit\n");
return 0;
}
static ssize_t adc_read(struct file *filp, char __user *buf, size_t count, loff_t *off)
{
int data = 0, ret = 0;
printk("adc_read\n");
adc_base->ADCMUX = 0x00;
adc_base->ADCCON = (1 << 16 | 1 << 14 | 99 << 6 | 1 << 0);
wait_event_interruptible(wait, ((adc_base->ADCCON >> 15) & 0x01));
data = adc_base->ADCDAT & 0xfff;
ret = copy_to_user(buf, &data, count);
printk("copy_to_user %x\n", data);
if (ret < 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
return count;
}
static struct file_operations adc_fops =
{
.owner = THIS_MODULE,
.open = adc_open,
.read = adc_read,
.release = adc_release,
};
static irqreturn_t adc_demo_isr(int irq, void *dev_id)
{
printk("enter irq now to wake up\n");
wake_up(&wait);
/* clear irq */
adc_base->CLRINTADC = 1;
return IRQ_HANDLED;
}
struct clk *base_clk;
int irq;
static int adc_probe(struct platform_device *pdev)
{
dev_t devid;
struct device *dev = &pdev->dev;
struct resource *res = NULL;
int ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL)
{
printk("platform_get_resource error\n");
return -EINVAL;
}
base_clk = devm_clk_get(&pdev->dev, "timers");
if (IS_ERR(base_clk))
{
dev_err(dev, "failed to get timer base clk\n");
return PTR_ERR(base_clk);
}
ret = clk_prepare_enable(base_clk);
if (ret < 0)
{
dev_err(dev, "failed to enable base clock\n");
return -EINVAL;
}
printk("res: %x\n", (unsigned int)res->start);
adc_base = devm_ioremap_resource(&pdev->dev, res);
if (adc_base == NULL)
{
printk("devm_ioremap_resource error\n");
goto err_clk;
}
printk("adc_base: %x\n", (unsigned int)adc_base);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
{
dev_err(&pdev->dev, "no irq resource?\n");
goto err_clk;
}
ret = request_irq(irq, adc_demo_isr, 0, "adc", NULL);
if (ret < 0)
{
dev_err(dev, "failed to request_irq\n");
goto err_clk;
}
if (alloc_chrdev_region(&devid, 0, 1, "adc") < 0)
{
printk("%s ERROR\n", __func__);
goto err_req_irq;
}
major = MAJOR(devid);
cdev_init(&adc_cdev, &adc_fops);
cdev_add(&adc_cdev, devid, 1);
cls = class_create(THIS_MODULE, "myadc");
device_create(cls, NULL, MKDEV(major, 0), NULL, "adc");
return 0;
err_req_irq:
free_irq(irq, NULL);
err_clk:
clk_disable(base_clk);
clk_unprepare(base_clk);
return -EINVAL;
}
static int adc_remove(struct platform_device *pdev)
{
printk("enter %s\n", __func__);
device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
cdev_del(&adc_cdev);
unregister_chrdev_region(MKDEV(major, 0), 1);
clk_disable(base_clk);
clk_unprepare(base_clk);
free_irq(irq, NULL);
printk("%s enter.\n", __func__);
return 0;
}
static const struct of_device_id adc_dt_ids[] =
{
{ .compatible = "tiny4412,adc_demo", },
{},
};
MODULE_DEVICE_TABLE(of, adc_dt_ids);
static struct platform_driver adc_driver =
{
.driver = {
.name = "adc_demo",
.of_match_table = of_match_ptr(adc_dt_ids),
},
.probe = adc_probe,
.remove = adc_remove,
};
static int adc_init(void)
{
int ret;
printk("enter %s\n", __func__);
ret = platform_driver_register(&adc_driver);
if (ret)
{
printk(KERN_ERR "adc demo: probe faiadc: %d\n", ret);
}
return ret;
}
static void adc_exit(void)
{
printk("enter %s\n", __func__);
platform_driver_unregister(&adc_driver);
}
module_init(adc_init);
module_exit(adc_exit);
MODULE_LICENSE("GPL");