linux-3.0中的触摸屏驱动讲解
来源:互联网 发布:cdn网络加速技术 编辑:程序博客网 时间:2024/05/14 23:03
一.ADC及触摸屏接口图
S3C2440有8路的ADC通道其中触摸屏控制器接口XP,XM,YP,YM与四路ADC通道复用四个IO引脚。从原理图看出8路ADC只有一个A/D转换器,通过一个8选1开关MUX来选通哪一路A/D通道进行转换。触摸屏控制会产生两个中断,一个触摸屏中断INT_TC,一个ADC_转换完成中断INT_ADC。ADC需要时钟才能工作,因为它需要设置采样率。
触摸屏工作流程:
1、选择模式
2、设置触摸屏接口到等待接口状态
3、如果中断发生,激活转换模式
4、获取坐标后,返回等待中断状态
(INT_TC中断用于按下或弹起触摸屏)
(INT_ADC用于坐标转换完成)
二.AD转换时间
当全局时钟频率为50MHz和预分频值为49时,总共10位转换时间如下:
AD转换器频率=50MHz/(49+10=1MHz
转换时间=1/(1MHz/5cycles)=1/200KHz=5us
三.ADC及触摸屏接口特殊寄存器
(1)控制寄存器(ADCCON)
(2)触摸屏控制寄存器(ADCTSC)
(3)开始延时寄存器(ADCDLY)
(4)转换数据寄存器0(ADCDAT0)
(5)转换数据寄存器1(ADCDATA1)
(6)触摸屏指针上下中断检测寄存器(ADCUPDN)
四.触摸屏接口模式:
(1)正常转换模式:通过设置ADCCON来初始化对ADCDATA0的读写操作。
(2)分离XY坐标转换模式:X坐标模式写X坐标转换数据到ADCDAT0,触摸屏接口产生中断源到中断控制器.Y坐标模式写Y坐标转换数据到ADCDAT1,触摸屏接口产生中断源到中断控制器
(3)自动XY坐标转换模式:触摸屏控制器连续转换触摸X坐标和Y坐标.在触摸控制器写X测量数据到ADCDAT0且写Y测量数据到ADCDAT1后,触摸屏接口产生中断源到自动转换模式下的中断控制器.
(4)等待中断模式:当光标按下产生中断信号(INT_TC)。触摸屏控制器的等待中断模式必须设定为触摸屏接口中触点的状态(XP,XM,YP,YM)
触摸屏可以看成是输入设备,工作原理是底层在触摸动作发送时产生一个中断,然后CPU通过外部存储器总线读取坐标,放入一个缓冲区,字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取坐标.
Input子系统处理输入事务,任何输入设备的驱动程序都可以通过Input输入子系统提供的接口注册到内核,利用子系统提供的功能来与用户空间交互。输入设备一般包括键盘,鼠标,触摸屏等,在内核中都是以输入设备出现的。下面分析input输入子系统的结构,以及功能实现。
一. Input子系统结构与功能实现
1. Input子系统是分层结构的,总共分为三层: 硬件驱动层,子系统核心层,事件处理层。
(1)其中硬件驱动层负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,需要驱动程序的作者来编写。
(2)子系统核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。
(3)事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。
2. 各层之间通信的基本单位就是事件,任何一个输入设备的动作都可以抽象成一种事件,如键盘的按下,触摸屏的按下,鼠标的移动等。事件有三种属性:类型(type),编码(code),值(value),Input子系统支持的所有事件都定义在input.h中,包括所有支持的类型,所属类型支持的编码等。事件传送的方向是 硬件驱动层-->子系统核心-->事件处理层-->用户空间
3.输入子系统设备驱动层实现原理:
在Linux中,Input设备用input_dev结构体描述,定义在input.h中。设备的驱动只需按照如下步骤就可实现了。
①、在驱动模块加载函数中设置Input设备支持input子系统的哪些事件;
②、将Input设备注册到input子系统中;
③、在Input设备发生输入操作时(如:键盘被按下/抬起、触摸屏被触摸/抬起/移动、鼠标被移动/单击/抬起时等),提交所发生的事件及对应的键值/坐标等状态。
Linux中输入设备的事件类型有(这里只列出了常用的一些,更多请看linux/input.h中):
- EV_SYN 0x00 同步事件
- EV_KEY 0x01 按键事件
- EV_REL 0x02 相对坐标(如:鼠标移动,报告的是相对最后一次位置的偏移)
- EV_ABS 0x03 绝对坐标(如:触摸屏和操作杆,报告的是绝对的坐标位置)
- EV_MSC 0x04 其它
- EV_LED 0x11 LED
- EV_SND 0x12 声音
- EV_REP 0x14 Repeat
- EV_FF 0x15 力反馈
4. 以触摸屏为例说明输入子系统的工作流程:
注:s3c2440的触摸屏驱动所用驱动层对应的模块文件为:s3c2410_ts.c,事件处理层对应的模块文件为 evdev.c
(1)s3c2410_ts模块初始化函数中将触摸屏注册到了输入子系统中,于此同时,注册函数在事件处理层链表中寻找事件处理器,这里找到的是evdev,并且将驱动与事件处理器挂载。并且在/dev/input中生成设备文件event0,以后我们访问这个文件就会找的我们的触摸屏驱动程序。
(2)应用程序打开设备文件/dev/input/event0,读取设备文件,调用evdev模块中read,如果没有事件进程就会睡眠。
(3)当触摸屏按下,驱动层通过子系统核心将事件(就是X,Y坐标),传给事件处理层也就是evdev,evdev唤醒睡眠的进程,将事件传给进程处理。
触摸屏代码分析
在arch/arm/plat-s3c24xx/devs.c定义了触摸屏的资源和platform_device结构体
static struct resource s3c_ts_resource[] = {
[0] = {
.start = SAMSUNG_PA_ADC, /*控制寄存器的起始地址0X58000000*/
.end = SAMSUNG_PA_ADC + SZ_ADC - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_TC,
.end = IRQ_TC,
.flags = IORESOURCE_IRQ,
},
};
struct platform_device s3c_device_ts = {
.name = "s3c2410-ts",
.id = -1,
.dev.parent = &s3c_device_adc.dev,
.num_resources = ARRAY_SIZE(s3c_ts_resource),
.resource = s3c_ts_resource,
};
EXPORT_SYMBOL(s3c_device_ts);
还有一个void __init s3c24xx_ts_set_platdata(struct s3c2410_ts_mach_info *hard_s3c2410ts_info)函数,该函数的作用是将struct s3c2410_ts_mach_info保存到触摸屏的平台数据中
s3c2410_ts_indo结构体的初始化在/arch/arm/mach-s3c2440/mach-smdk2440.c里
static struct s3c2410_ts_mach_info smdk2440_ts_cfg __initdata = {
.delay = 10000, /*ADC的延迟时间*/
.presc = 49, /*ADC的预分频系数*/
.oversampling_shift = 2, /*采样次数log2的值*/
};
在smdk2440_devices[]这个platform_device结构体数组里面添加s3c_device_ts和s3c_device_adc。内核会将该结构体了的成员全部加到platform总线上.
在smdk2440_machine_init中调用s3c24xx_ts_set_platdata(&smdk2440_ts_cfg),这样就把上面的结构体信息保存到了平台数据中.
下面分析drivers/input/touchscreen/s3c2410_ts.c
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <plat/adc.h>
#include <plat/regs-adc.h>
#include <plat/ts.h>
#define TSC_SLEEP (S3C2410_ADCTSC_PULL_UP_DISABLE | S3C2410_ADCTSC_XY_PST(0))
#define INT_DOWN (0)
#define INT_UP (1 << 8)
#define WAIT4INT (S3C2410_ADCTSC_YM_SEN | \
S3C2410_ADCTSC_YP_SEN | \
S3C2410_ADCTSC_XP_SEN | \
S3C2410_ADCTSC_XY_PST(3))
#define AUTOPST (S3C2410_ADCTSC_YM_SEN | \
S3C2410_ADCTSC_YP_SEN | \
S3C2410_ADCTSC_XP_SEN | \
S3C2410_ADCTSC_AUTO_PST | \
S3C2410_ADCTSC_XY_PST(0))
#define FEAT_PEN_IRQ (1 << 0) /* HAS ADCCLRINTPNDNUP */
struct s3c2410ts {
struct s3c_adc_client *client; /*触摸屏驱动是利用ADC转换坐标值 所以 要将触摸屏驱动挂接到ADC上 即 我们这里要说的将ADC作为server 将触摸屏做为client */
struct device *dev;
struct input_dev *input;
struct clk *clock;
void __iomem *io;
unsigned long xp;
unsigned long yp;
int irq_tc;
int count;
int shift;
int features;
};
static struct s3c2410ts ts;
/*
#define S3C2410_ADCDAT0_UPDOWN (1<<15)
取ADCDAT0的第15位与ADCDAT1的第15位相与的结果。只有当两个寄存器的第15位t同时为0时才返回1,否则返回0.从s3c2440的手册查得ADCDAT的第15位表示对与等待中断模式的光标按下或提起状态,0表示光标被按下状态,1表示光标被提起状态.所以此函数返回1表示按下.
*/
static inline bool get_down(unsigned long data0, unsigned long data1)
{
/* returns true if both data values show stylus down */
return (!(data0 & S3C2410_ADCDAT0_UPDOWN) &&
!(data1 & S3C2410_ADCDAT0_UPDOWN));
}
static void touch_timer_fire(unsigned long data)
{
unsigned long data0;
unsigned long data1;
bool down;
data0 = readl(ts.io + S3C2410_ADCDAT0); /*获取ADCDAT0里的值*/
data1 = readl(ts.io + S3C2410_ADCDAT1); /*获取ADCDAT1里的值*/
down = get_down(data0, data1);
if (down) { /*如果光标为按下状态*/
if (ts.count == (1 << ts.shift)) { /*这里的ts.shift=2,在下面的s3c2410ts_probe里有ts.shift = info->oversampling_shift,我们前面在初始化s3c2410_ts_mach_info是将oversampling_shift设置为2,所以这里的条件是采样次数等于4时*/
ts.xp >>= ts.shift; /*求平均值*/
ts.yp >>= ts.shift;
dev_dbg(ts.dev, "%s: X=%lu, Y=%lu, count=%d\n",
__func__, ts.xp, ts.yp, ts.count);
/*提交X,Y的绝对坐标*/
input_report_abs(ts.input, ABS_X, ts.xp);
input_report_abs(ts.input, ABS_Y, ts.yp);
input_report_key(ts.input, BTN_TOUCH, 1); /*提交按键事件,键值为1表示触摸屏对应的按键被按下*/
input_report_abs(ts.input, ABS_PRESSURE, 1); /* 提交触摸屏的状态,1表示触摸屏被按下Add by guowenxue, 2012.03.30 */
input_sync(ts.input); /*等待接受方收到数据后回复确认,用于同步*/
ts.xp = 0;
ts.yp = 0;
ts.count = 0; /*当转换次数为4时,将count重新置为0*/
}
s3c_adc_start(ts.client, 0, 1 << ts.shift); /*开启AD转换*/
} else {
ts.xp = 0;
ts.yp = 0;
ts.count = 0;
input_report_key(ts.input, BTN_TOUCH, 0);
input_report_abs(ts.input, ABS_PRESSURE, 0); /* Add by guowenxue, 2012.03.30 */
input_sync(ts.input);
writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC); /*设置成等待中断模式*/
}
}
/*这里为什么要添加这个内核定时器.因为当我们按下一次,只会产生一个中断,如果我们滑动的话,就不会产生任何效果.为了处理滑动的情况,我们就启用定时器,每隔一段时间就启用一次AD转换,这样就可以得到按下点的信息了。*/
static DEFINE_TIMER(touch_timer, touch_timer_fire, 0, 0); /*创建内核定时器*/
/**
* stylus_irq - touchscreen stylus event interrupt
* @irq: The interrupt number
* @dev_id: The device ID.
*
* Called when the IRQ_TC is fired for a pen up or down event.
*/
static irqreturn_t stylus_irq(int irq, void *dev_id)
{
unsigned long data0;
unsigned long data1;
bool down;
data0 = readl(ts.io + S3C2410_ADCDAT0);
data1 = readl(ts.io + S3C2410_ADCDAT1);
down = get_down(data0, data1);
/* TODO we should never get an interrupt with down set while
* the timer is running, but maybe we ought to verify that the
* timer isn't running anyways. */
if (down) /*如果触摸屏被按下开启AD转换*/
s3c_adc_start(ts.client, 0, 1 << ts.shift); /*设置client中的channel(使用通道为)0,nr_samples为4*/
else
dev_dbg(ts.dev, "%s: count=%d\n", __func__, ts.count);
if (ts.features & FEAT_PEN_IRQ) {
/* Clear pen down/up interrupt */
writel(0x0, ts.io + S3C64XX_ADCCLRINTPNDNUP);
}
return IRQ_HANDLED;
}
**
* s3c24xx_ts_conversion - ADC conversion callback
* @client: The client that was registered with the ADC core.
* @data0: The reading from ADCDAT0.
* @data1: The reading from ADCDAT1.
* @left: The number of samples left.
*
* Called when a conversion has finished.
*/
/*s3c24xx_ts_conversion的作用就是累加每次采样后得到的x,y坐标值,并记录采样次数,
在其它函数中,可以取ts.xp和ts.yp的平均值,得到传递给用户空间的x,y坐标值。*/
static void s3c24xx_ts_conversion(struct s3c_adc_client *client,
unsigned data0, unsigned data1,
unsigned *left)
{
dev_dbg(ts.dev, "%s: %d,%d\n", __func__, data0, data1);
ts.xp += data0;
ts.yp += data1;
ts.count++;
/* From tests, it seems that it is unlikely to get a pen-up
* event during the conversion process which means we can
* ignore any pen-up events with less than the requisite
* count done.
*
* In several thousand conversions, no pen-ups where detected
* before count completed.
*/
}
/**
* s3c24xx_ts_select - ADC selection callback.
* @client: The client that was registered with the ADC core.
* @select: The reason for select.
*
* Called when the ADC core selects (or deslects) us as a client.
*/
static void s3c24xx_ts_select(struct s3c_adc_client *client, unsigned select)
{
/*select为非0时设置为自动连续测量x坐标和y坐标模式*/
if (select) {
writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,
ts.io + S3C2410_ADCTSC); /*设置成自动连续测量X坐标和Y坐标*/
} else {
mod_timer(&touch_timer, jiffies+1);
writel(WAIT4INT | INT_UP, ts.io + S3C2410_ADCTSC); /*设置成抬起触发中断*/
}
}
/**
* s3c2410ts_probe - device core probe entry point
* @pdev: The device we are being bound to.
*
* Initialise, find and allocate any resources we need to run and then
* register with the ADC and input systems.
*/
static int __devinit s3c2410ts_probe(struct platform_device *pdev)
{
struct s3c2410_ts_mach_info *info;
struct device *dev = &pdev->dev;
struct input_dev *input_dev;
struct resource *res;
int ret = -EINVAL;
/* Initialise input stuff */
memset(&ts, 0, sizeof(struct s3c2410ts));
ts.dev = dev;
info = pdev->dev.platform_data;
if (!info) {
dev_err(dev, "no platform data, cannot attach\n");
return -EINVAL;
}
dev_dbg(dev, "initialising touchscreen\n");
ts.clock = clk_get(dev, "adc"); /*获取ADC时钟*/
if (IS_ERR(ts.clock)) {
dev_err(dev, "cannot get adc clock source\n");
return -ENOENT;
}
clk_enable(ts.clock); /*时钟使能*/
dev_dbg(dev, "got and enabled clocks\n");
ts.irq_tc = ret = platform_get_irq(pdev, 0); /*获取中断号*/
if (ret < 0) {
dev_err(dev, "no resource for interrupt\n");
goto err_clk;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); /*获取资源*/
if (!res) {
dev_err(dev, "no resource for registers\n");
ret = -ENOENT;
goto err_clk;
}
ts.io = ioremap(res->start, resource_size(res)); /*I/O内存映射*/
if (ts.io == NULL) {
dev_err(dev, "cannot map registers\n");
ret = -ENOMEM;
goto err_clk;
}
/* inititalise the gpio */
if (info->cfg_gpio) /*如果有cfg_gpio,就调用,初始化gpio*/
info->cfg_gpio(to_platform_device(ts.dev));
/*注册ADC设备*/
ts.client = s3c_adc_register(pdev, s3c24xx_ts_select,
s3c24xx_ts_conversion, 1);
if (IS_ERR(ts.client)) {
dev_err(dev, "failed to register adc client\n");
ret = PTR_ERR(ts.client);
goto err_iomap;
}
/* Initialise registers */
/*设置ADC的延时寄存器*/
if ((info->delay & 0xffff) > 0)
writel(info->delay & 0xffff, ts.io + S3C2410_ADCDLY);
writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC); /*设置成等待中断模式*/
input_dev = input_allocate_device(); /分配一个input_dev*/
if (!input_dev) {
dev_err(dev, "Unable to allocate the input device !!\n");
ret = -ENOMEM;
goto err_iomap;
}
/*初始化input*/
ts.input = input_dev;
ts.input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) | BIT(EV_SYN); /* Modify by
guowenxue, 2012.03.30, 每一种类型的事件都在input_dev.evbit中用一位来表示,构成一个位图,如果某一位为1,表示支持该事件,如果该位为0,表示不支持该事件*/
ts.input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); /*设置所支持的按键,触摸屏可以看成是只有一个按键的设备*/
/* 设置ad转换的XY坐标,范围是0-0x3FF
因为mini2440的AD转换出的数据最大为10位,所以不会超过0x3ff。
*/
input_set_abs_params(ts.input, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0);
input_set_abs_params(ts.input, ABS_PRESSURE, 0, 1, 0, 0); /* Add by guowenxue, 2012.03.
30 */
ts.input->name = "S3C24XX TouchScreen";
ts.input->id.bustype = BUS_HOST;
ts.input->id.vendor = 0xDEAD;
ts.input->id.product = 0xBEEF;
ts.input->id.version = 0x0102;
ts.shift = info->oversampling_shift;
ts.features = platform_get_device_id(pdev)->driver_data;
ret = request_irq(ts.irq_tc, stylus_irq, IRQF_DISABLED,
"s3c2410_ts_pen", ts.input);
if (ret) {
dev_err(dev, "cannot get TC interrupt\n");
goto err_inputdev;
}
dev_info(dev, "driver attached, registering input device\n");
/* All went ok, so register to the input system */
ret = input_register_device(ts.input);
if (ret < 0) {
dev_err(dev, "failed to register input device\n");
ret = -EIO;
goto err_tcirq;
}
return 0;
err_tcirq:
free_irq(ts.irq_tc, ts.input);
err_inputdev:
input_free_device(ts.input);
err_iomap:
iounmap(ts.io);
err_clk:
del_timer_sync(&touch_timer);
clk_put(ts.clock);
return ret;
}
static int __devexit s3c2410ts_remove(struct platform_device *pdev)
{
free_irq(ts.irq_tc, ts.input);
del_timer_sync(&touch_timer);
clk_disable(ts.clock);
clk_put(ts.clock);
input_unregister_device(ts.input);
iounmap(ts.io);
return 0;
}
#ifdef CONFIG_PM
static int s3c2410ts_suspend(struct device *dev)
{
writel(TSC_SLEEP, ts.io + S3C2410_ADCTSC);
disable_irq(ts.irq_tc);
clk_disable(ts.clock);
return 0;
}
static int s3c2410ts_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct s3c2410_ts_mach_info *info = pdev->dev.platform_data;
clk_enable(ts.clock);
enable_irq(ts.irq_tc);
/* Initialise registers */
if ((info->delay & 0xffff) > 0)
writel(info->delay & 0xffff, ts.io + S3C2410_ADCDLY);
writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);
return 0;
}
static struct dev_pm_ops s3c_ts_pmops = {
.suspend = s3c2410ts_suspend,
.resume = s3c2410ts_resume,
};
#endif
static struct platform_device_id s3cts_driver_ids[] = {
{ "s3c2410-ts", 0 },
{ "s3c2440-ts", 0 },
{ "s3c64xx-ts", FEAT_PEN_IRQ },
{ }
};
MODULE_DEVICE_TABLE(platform, s3cts_driver_ids);
static struct platform_driver s3c_ts_driver = {
.driver = {
.name = "samsung-ts",
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = &s3c_ts_pmops,
#endif
},
.id_table = s3cts_driver_ids,
.probe = s3c2410ts_probe,
.remove = __devexit_p(s3c2410ts_remove),
};
static int __init s3c2410ts_init(void)
{
return platform_driver_register(&s3c_ts_driver);
}
static void __exit s3c2410ts_exit(void)
{
platform_driver_unregister(&s3c_ts_driver);
}
module_init(s3c2410ts_init);
module_exit(s3c2410ts_exit);
MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>, "
"Ben Dooks <ben@simtec.co.uk>, "
"Simtec Electronics <linux@simtec.co.uk>");
MODULE_DESCRIPTION("S3C24XX Touchscreen driver");
MODULE_LICENSE("GPL v2");
下面总结一下驱动原理:当触摸屏被按下时,首先会触发触摸屏中断,该中断是在s3c2410ts_probe函数中申请的ret = request_irq(ts.irq_tc, stylus_irq, IRQF_DISABLED,
"s3c2410_ts_pen", ts.input);中断处理函数为stylus_irq,在该函数内首先判断屏幕是否被按下,按下则调用s3c_adc_start函数开始AD转换。转换完成后又会触发ADC中断,进入ADC的中断服务程序s3c_adc_irq.在ADC的中断服务程序中会调用客服端的convert_cb函数,即上面的s3c24xx_ts_conversion函数,该函数的作用就是累计X,Y坐标的值。然后判断转换次数到了没,前面我们把转换次数设置成了4,每次进入这个函数时就将转换次数减一,没到就调用client->select_cb(client, 1),即上面的s3c24xx_ts_select,设置成自动连续测量X坐标和Y坐标函数,然后进行ADC转换,转换后又产生ADC中断,又进入到ADC中断服务程序,如此循环直到进行了4次AD转换.最后调用(client->select_cb)(client, 0),进入定时器处理函数,这里说明一下为什么能调用到s3c24xx_ts_conversion和convert_cb的,因为在触摸屏驱动中我们向server端注册了一个client sc32410_ts.c中的probe函数中ts.client = s3c_adc_register(pdev, s3c24xx_ts_select,s3c24xx_ts_conversion, 1);。定时器处理函数 首先判断触摸屏是按下还是抬起 如果是按下则算出x轴和y轴坐标值的平均值(ts.xp >>= ts.shift;ts.yp >>= ts.shift;注意右移2位表示除以4) 并把结果上报给input子系统 然后又继续调用s3c_adc_start函数开始ADC转换 这样一直循环 如果是抬起 则进入触摸屏中断服务程序stylus_irq(因为前面已经把触摸屏中断触发方式改为抬起了)
到这里 触摸屏驱动基本上就分析完了 其实 如果要扩展的话 东西太多 大家有兴趣也可以自己研究 个人认为 原理很简单 代码花点时间也能看明白 而最难的最关键的我认为还是驱动架构 为什么linux要这么做这个驱动 这么做有什么好处 就触摸屏驱动而言 因为他要用到ADC 而还有很多地方也要用到ADC 比如 HWMON驱动 那么如果把ADC单独拿出来作为server的话 其他使用ADC的设备作为client 这样就不会形成累赘的代码 可移植性也越好 PWM也是一样 LCD 背光 蜂鸣器都要用到PWM 因此也就采用这种server和client结构
- linux-3.0中的触摸屏驱动讲解
- linux s3c2410触摸屏驱动讲解
- 触摸屏驱动实例开发讲解
- linux 触摸屏驱动分析
- Linux 触摸屏驱动
- Linux触摸屏驱动
- s3c2440 linux 触摸屏驱动
- linux 触摸屏驱动分析
- Linux 触摸屏驱动分析
- linux触摸屏驱动分析
- linux 触摸屏 驱动
- linux "电阻"触摸屏驱动
- linux 触摸屏驱动分析
- linux 触摸屏驱动分析
- linux 触摸屏驱动分析
- linux 触摸屏驱动分析
- linux驱动摸索-- 触摸屏
- Linux驱动之触摸屏
- SQL语句专题
- 1001. A+B Format (20)
- Reorder List[LeetCode]
- php中escape和unescape
- Oracle递归 (转)
- linux-3.0中的触摸屏驱动讲解
- 旧版本NDK的下载
- Oracle表分区.
- [转]写给开发人员的话
- 时间复杂度和空间复杂度整理
- Nginx+PHP+mysql
- 斯坦福大学-朴素贝叶斯_Exercise Code
- sql关于重复记录
- jquery.validate入门demo