adc驱动文件路径:arch/arm/plat-samsung/adc.c
adc设备文件路径:arch/arm/plat-samsung/devs.c
A/D转换器和触摸屏接口的功能框图
s3c2416有10路adc通道,其中XP、XM、YP、YM分别与AIN9、AIN8、AIN7、AIN6复用。由于只有1个A/D转换器,需要通过一个10选1开关MUX,来选通哪一路A/D通道进行转换。
触摸屏的中断产生单元会产生2个中断,INT_ADC和INT_TC。触摸屏工作在中断等待模式会等待中断INT_TC,INT_TC发生后,立刻激活相应的A/D转换,INT_ADC是A/D转换完成的中断信号,得到触摸点的坐标后,返回等待中断等待模式。
ADCCON和ADCMUX寄存器
看驱动代码当然从module_init开始
-
static int __init adc_init(void)
-
{
-
int ret;
-
-
ret = platform_driver_register(&s3c_adc_driver);//注册平台驱动,s3c_adc_driver
-
if (ret)
-
printk(KERN_ERR "%s: failed to add adc driver\n", __func__);
-
-
return ret;
-
}
这段代码很简单,想platform注册了s3c_adc_driver,它的定义如下
-
static struct platform_driver s3c_adc_driver = {
-
.id_table = s3c_adc_driver_ids,//支持的设备列表
-
.driver = {
-
.name = "s3c-adc",
-
.owner = THIS_MODULE,
-
},
-
.probe = s3c_adc_probe,//注册设备时执行
-
.remove = __devexit_p(s3c_adc_remove),
-
.suspend = s3c_adc_suspend,
-
.resume = s3c_adc_resume,
-
};
注意,当id_table不为NULL时,是先拿id_talbe保存的名字和设备的名字匹配,如果没有匹配,再去拿驱动的名字和设备的名字匹配!
看看id_talbe
-
static struct platform_device_id s3c_adc_driver_ids[] = {
-
{
-
.name = "s3c24xx-adc",
-
.driver_data = TYPE_S3C24XX,
-
}, {
-
.name = "s3c64xx-adc",
-
.driver_data = TYPE_S3C64XX,
-
},
-
{ }
-
};
-
MODULE_DEVICE_TABLE(platform, s3c_adc_driver_ids);
最后一行的MODULE_DEVICE_TABLE是啥?为什么结构体最后要加个空的{}呢?
MODULE_DEVICE_TABLE一般用在热插拔的设备驱动中。
上述xxx_driver_ids结构,是此驱动所支持的设备列表。
作用是:将xxx_driver_ids结构输出到用户空间,这样模块加载系统在加载模块时,就知道了什么模块对应什么硬件设备。
用法是:MODULE_DEVICE_TABLE(设备类型,设备表),其中,设备类型,包括USB,PCI等,也可以自己起名字,上述代码中是针对不同的平台分的类;设备表也是自己定义的,它的最后一项必须是空,用来标识结束。
这个MODULE_DEVICE_TABLE功能的解释是别人文章里拷贝过来的。。。
platform bus做到了设备与总线的分离,由Linux内核进行统一管理
宋宝华:隔离BSP和驱动。在BSP中定义设备和设备使用的资源(IO资源、中断资源等)、设备的具体配置信息,而在驱动中,只需要通过通用API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动有更好的可扩展性和跨平台性。
从宋老师的话中,我们知道,驱动中是不涉及具体硬件的配置的,用通用API去获取,怎么获取呢?看s3c_adc_driver中的s3c_adc_probe吧
s3c_adc_probe
-
static int s3c_adc_probe(struct platform_device *pdev)
-
{
-
struct device *dev = &pdev->dev;
-
struct adc_device *adc;//定义一个 struct adc_device代表一个adc设备
-
struct resource *regs;//获得资源,IO与中断
-
int ret;
-
unsigned tmp;
-
-
adc = kzalloc(sizeof(struct adc_device), GFP_KERNEL);//动态分配ADC空间
-
if (adc == NULL) {
-
dev_err(dev, "failed to allocate adc_device\n");
-
return -ENOMEM;
-
}
-
-
spin_lock_init(&adc->lock);//初始化自旋锁
-
-
adc->pdev = pdev;
-
adc->prescale = S3C2410_ADCCON_PRSCVL(49);//设置预分频系数
-
-
adc->irq = platform_get_irq(pdev, 1);//获得ADC中断号
-
if (adc->irq <= 0) {
-
dev_err(dev, "failed to get adc irq\n");
-
ret = -ENOENT;
-
goto err_alloc;
-
}
-
-
ret = request_irq(adc->irq, s3c_adc_irq, 0, dev_name(dev), adc);//注册中断
-
if (ret < 0) {
-
dev_err(dev, "failed to attach adc irq\n");
-
goto err_alloc;
-
}
-
-
adc->clk = clk_get(dev, "adc");//获取ADC时钟
-
if (IS_ERR(adc->clk)) {
-
dev_err(dev, "failed to get adc clock\n");
-
ret = PTR_ERR(adc->clk);
-
goto err_irq;
-
}
-
-
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);//获得设备的IO资源
-
if (!regs) {
-
dev_err(dev, "failed to find registers\n");
-
ret = -ENXIO;
-
goto err_clk;
-
}
-
-
adc->regs = ioremap(regs->start, resource_size(regs));//映射IO到虚拟地址
-
if (!adc->regs) {
-
dev_err(dev, "failed to map registers\n");
-
ret = -ENXIO;
-
goto err_clk;
-
}
-
-
clk_enable(adc->clk);//使能时钟
-
-
tmp = adc->prescale | S3C2410_ADCCON_PRSCEN;//设置使用预分频
-
if (platform_get_device_id(pdev)->driver_data == TYPE_S3C64XX) {
-
/* Enable 12-bit ADC resolution */
-
tmp |= S3C64XX_ADCCON_RESSEL;
-
}
-
writel(tmp, adc->regs + S3C2410_ADCCON);
-
-
dev_info(dev, "attached adc driver\n");
-
-
platform_set_drvdata(pdev, adc);
-
adc_dev = adc;
-
-
return 0;
-
-
err_clk:
-
clk_put(adc->clk);
-
-
err_irq:
-
free_irq(adc->irq, adc);
-
-
err_alloc:
-
kfree(adc);
-
return ret;
-
}
先看s3c_adc_probe的参数,struct platform_device *pdev,一个结构体直针,驱动注册时会匹配设备,struct platform_device就是用来描述设备的结构体。
adc设备的描述定义在arch/arm/plat-samsung/dev-adc.c。我们转移阵地,去看看里面的定义。
-
static struct resource s3c_adc_resource[] = {
-
[0] = {
-
.start = SAMSUNG_PA_ADC,
-
.end = SAMSUNG_PA_ADC + SZ_256 - 1,
-
.flags = IORESOURCE_MEM,
-
},
-
[1] = {
-
.start = IRQ_TC,
-
.end = IRQ_TC,
-
.flags = IORESOURCE_IRQ,
-
},
-
[2] = {
-
.start = IRQ_ADC,
-
.end = IRQ_ADC,
-
.flags = IORESOURCE_IRQ,
-
},
-
};
-
-
struct platform_device s3c_device_adc = {
-
.name = "samsung-adc",
-
.id = -1,
-
.num_resources = ARRAY_SIZE(s3c_adc_resource),
-
.resource = s3c_adc_resource,
-
};
回到s3c_adc_probe
设置预分频系数使用了一个宏,它将49左移6位,因为ADCCON寄存器的6到13为代表预分频系数;
使用tmp写ADCCON寄存器,先将14位置1,使能预分频功能,将第3位置1,设置分辨率为12位。
kzalloc为adc动态分配空间。看看struct adc_device的定义:
-
struct adc_device {
-
struct platform_device *pdev;//平台设备信息
-
struct platform_device *owner;
-
struct clk *clk;//时钟
-
struct s3c_adc_client *cur;//当前正在处理的客户
-
struct s3c_adc_client *ts_pend;//代表触摸屏
-
void __iomem *regs;//ADC的I/O寄存器
-
spinlock_t lock;//自旋锁
-
-
unsigned int prescale;//预分频
-
-
int irq;//adc中断号
-
};
这里有个struct s3c_adc_client结构,ts_pend代表触摸屏,这里ADC把客户分为触摸屏和非触摸屏两大类,专门用ts_pend代表触摸屏,struct s3c_adc_client定义如下:
-
struct s3c_adc_client {//代表了一个请求ADC服务的客户
-
struct platform_device *pdev;//平台设备结构体
-
struct list_head pend;//链表,将客户插入等待链表
-
wait_queue_head_t *wait;//等待队列头
-
-
unsigned int nr_samples;//记录客户指定的采样次数
-
int result;//记录采样结构
-
unsigned char is_ts;//是不是触摸屏
-
unsigned char channel;//客户要使用的ADC通道
-
-
void (*select_cb)(struct s3c_adc_client *c, unsigned selected);//select_cb回调函数,用来选择客户和取消客户
-
void (*convert_cb)(struct s3c_adc_client *c,
-
unsigned val1, unsigned val2,
-
unsigned *samples_left);//convert_cb回调函数,用于处理A/D转换结果,
-
}
s3c_adc_probe就到这里
客户注册ADC服务的接口函数s3c_adc_register
-
struct s3c_adc_client *s3c_adc_register(struct platform_device *pdev,
-
void (*select)(struct s3c_adc_client *client,
-
unsigned int selected),
-
void (*conv)(struct s3c_adc_client *client,
-
unsigned d0, unsigned d1,
-
unsigned *samples_left),
-
unsigned int is_ts)
-
{
-
struct s3c_adc_client *client;
-
-
WARN_ON(!pdev);
-
-
if (!select)//如果没有传select回调函数,则使用默认的回调函数
-
select = s3c_adc_default_select;
-
-
if (!pdev)
-
return ERR_PTR(-EINVAL);
-
-
client = kzalloc(sizeof(struct s3c_adc_client), GFP_KERNEL);//动态分配空间
-
if (!client) {
-
dev_err(&pdev->dev, "no memory for adc client\n");
-
return ERR_PTR(-ENOMEM);
-
}
-
-
client->pdev = pdev;//初始化相关成员
-
client->is_ts = is_ts;
-
client->select_cb = select;
-
client->convert_cb = conv;
-
-
return client;
-
}
-
EXPORT_SYMBOL_GPL(s3c_adc_register);
WARN_ON调用dump_stack,打印堆栈信息
客户读取A/D转换结果,s3c_adc_read
-
int s3c_adc_read(struct s3c_adc_client *client, unsigned int ch)
-
{
-
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake);
-
int ret;
-
-
client->convert_cb = s3c_convert_done;//convert回调函数,用于处理A/D转换结果
-
client->wait = &wake;
-
client->result = -1;
-
-
ret = s3c_adc_start(client, ch, 1);//设置采用参数,通道为ch,次数均为1
-
if (ret < 0)
-
goto err;
-
-
ret = wait_event_timeout(wake, client->result >= 0, HZ / 2);//休眠,条件client->result >= 0为真,唤醒,最长睡眠时间为HZ/2
-
if (client->result < 0) {
-
ret = -ETIMEDOUT;
-
goto err;
-
}
-
-
client->convert_cb = NULL;
-
return client->result;
-
-
err:
-
return ret;
-
}
-
EXPORT_SYMBOL_GPL(s3c_adc_read);
A/D转换完成函数s3c_convert_done
-
static void s3c_convert_done(struct s3c_adc_client *client,
-
unsigned v, unsigned u, unsigned *left)
-
{
-
client->result = v;//记录采用结果
-
wake_up(client->wait);//唤醒等待队列中休眠的进程
-
}
设置采样参数s3c_adc_start
-
int s3c_adc_start(struct s3c_adc_client *client,
-
unsigned int channel, unsigned int nr_samples)
-
{
-
struct adc_device *adc = adc_dev;
-
unsigned long flags;
-
-
if (!adc) {
-
printk(KERN_ERR "%s: failed to find adc\n", __func__);
-
return -EINVAL;
-
}
-
-
if (client->is_ts && adc->ts_pend)
-
return -EAGAIN;
-
-
spin_lock_irqsave(&adc->lock, flags);//获得锁
-
-
client->channel = channel;//设置通道
-
client->nr_samples = nr_samples;//设置采用是次数
-
-
if (client->is_ts)//如果是触摸屏,将client保存在ts_pend中
-
adc->ts_pend = client;
-
else
-
list_add_tail(&client->pend, &adc_pending);//如果不是触摸屏,挂到链表
-
-
if (!adc->cur)//如果没有正在处理的其他客户请求,则调用s3c_adc_try
-
s3c_adc_try(adc);
-
-
spin_unlock_irqrestore(&adc->lock, flags);//释放锁
-
-
return 0;
-
}
spin_lock_irqsave()是一个很便利的接口,它有以下动作:1、保存本地中断状态;2、关闭本地中断;3、获取自旋锁
spin_lock_irqsave()适用于在中断处理函数中需要用到自旋锁。
解锁时通过 spin_unlock_irqrestore完成释放锁、恢复本地中断到之前的状态等工作。
还有一对 spin_lock_irq 和 spin_unlock_irq,如果你确定在获取锁之前本地中断是开启的,那么就不需要保存中断状态,解锁的时候直接将本地中断启用就可以啦。
处理当前客户请求s3c_adc_try
-
static void s3c_adc_try(struct adc_device *adc)
-
{
-
struct s3c_adc_client *next = adc->ts_pend;
-
-
if (!next && !list_empty(&adc_pending)) {
-
next = list_first_entry(&adc_pending,
-
struct s3c_adc_client, pend);
-
list_del(&next->pend);
-
} else
-
adc->ts_pend = NULL;
-
-
if (next) {
-
adc_dbg(adc, "new client is %p\n", next);
-
adc->cur = next;
-
s3c_adc_select(adc, next);
-
s3c_adc_convert(adc);
-
s3c_adc_dbgshow(adc);
-
}
-
}
这段代码很简单,没有给注释,大体的功能是先:检查有没有触摸屏在等待,如果有,if后的条件为假,直接执行140行,然后设置当前客户为触摸屏,调用select、convert、dbgshow处理客户的AD请求;如果没有触摸屏在等待,就去链表中检查有没有非触摸屏客户,链表为空,则结束。
下面就是select、convert、dbgshow的分析
s3c_adc_select
-
static inline void s3c_adc_select(struct adc_device *adc,
-
struct s3c_adc_client *client)
-
{
-
unsigned con = readl(adc->regs + S3C2410_ADCCON);
-
-
client->select_cb(client, 1);//执行客户自定义的回调函数
-
-
con &= ~S3C2410_ADCCON_MUXMASK;//清零操作
-
con &= ~S3C2410_ADCCON_STDBM;
-
con &= ~S3C2410_ADCCON_STARTMASK;
-
-
if (!client->is_ts)//如果是非触摸屏
-
con |= S3C2410_ADCCON_SELMUX(client->channel);
-
-
writel(con, adc->regs + S3C2410_ADCCON);//写ADCCON寄存器
-
}
s3c_adc_convert
-
static inline void s3c_adc_convert(struct adc_device *adc)
-
{
-
unsigned con = readl(adc->regs + S3C2410_ADCCON);//读ADCCON寄存器
-
-
con |= S3C2410_ADCCON_ENABLE_START;//将ADCCON的第0位置1,开启ADC转换
-
writel(con, adc->regs + S3C2410_ADCCON);//写ADCCON寄存器
-
}
对照前面的寄存器功能表,很容易就知道它就是开启了AD转换。。
s3c_adc_dbgshow
-
static void s3c_adc_dbgshow(struct adc_device *adc)
-
{
-
//打印三个寄存器的值
-
adc_dbg(adc, "CON=%08x, TSC=%08x, DLY=%08x\n",
-
readl(adc->regs + S3C2410_ADCCON),
-
readl(adc->regs + S3C2410_ADCTSC),
-
readl(adc->regs + S3C2410_ADCDLY));
-
}
s3c_adc_convert函数启动AD转换,转换结束后,会触发ADC中断,下面看ADC中断处理函数
s3c_adc_irq
-
static irqreturn_t s3c_adc_irq(int irq, void *pw)//中断处理函数
-
{
-
struct adc_device *adc = pw;
-
struct s3c_adc_client *client = adc->cur;//获得当前请求ADC服务的客户
-
enum s3c_cpu_type cpu = platform_get_device_id(adc->pdev)->driver_data;
-
unsigned data0, data1;
-
-
if (!client) {
-
dev_warn(&adc->pdev->dev, "%s: no adc pending\n", __func__);
-
goto exit;
-
}
-
-
data0 = readl(adc->regs + S3C2410_ADCDAT0);//读ADCDAT0
-
data1 = readl(adc->regs + S3C2410_ADCDAT1);//读ADCDAT1
-
adc_dbg(adc, "read %d: 0x%04x, 0x%04x\n", client->nr_samples, data0, data1);
-
-
client->nr_samples--;//采样次数-1
-
-
if (cpu == TYPE_S3C64XX) {
-
/* S3C64XX ADC resolution is 12-bit */
-
data0 &= 0xfff;
-
data1 &= 0xfff;
-
} else {
-
data0 &= 0x3ff;
-
data1 &= 0x3ff;
-
}
-
-
if (client->convert_cb)//如果指定了convert_cb回调函数,执行它处理A/D转换结果
-
(client->convert_cb)(client, data0, data1, &client->nr_samples);
-
-
if (client->nr_samples > 0)//再采样一次
-
{
-
/* fire another conversion for this */
-
-
client->select_cb(client, 1);
-
s3c_adc_convert(adc);//开启转换
-
} else {
-
spin_lock(&adc->lock);
-
(client->select_cb)(client, 0);//如果不需要采样,就取消对用户的选择
-
adc->cur = NULL;//设置当前客户为空
-
-
s3c_adc_try(adc);//检查是否还有客户在等待ADC服务,如果有,则其设置为当前客户,
并启动AD转换。*/
-
spin_unlock(&adc->lock);
-
}
-
-
exit:
-
if (cpu == TYPE_S3C64XX) {
-
/* Clear ADC interrupt */
-
writel(0, adc->regs + S3C64XX_ADCCLRINT);
-
}
-
return IRQ_HANDLED;
-
}
到此,ADC驱动分析结束
阅读(1842) | 评论(0) | 转发(0) |