一、芯片和驱动架构总体介绍
1.1、芯片介绍
本文使用的芯片为GSC3280,根据芯片手册的介绍,ADC 与触摸屏控制器通过 SPI 接口挂在GSC3280的SPI0总
线上,支持4线电阻式触摸屏或当ADC输入使用。GSC3280片上集成的ADC与触摸屏控制器主要有以下特性:
1、通过标准SPI接口传输命令和数据;
2、最大分辨率为12位;
3、输入最大的SPI 时钟是6MHz,对应最大采样率为120Ksps;
4、支持4线电阻式触摸屏;
5、可以当4路ADC输入;
6、支持触摸屏触笔中断;
7、支持低功耗模式;
1.2、驱动架构介绍
本文所设计的ADC子系统架构关系图如下:
由于AD转换使用的是SPI0,所以此处在Linux内核源码目录的/driver/spi目录下建立一个adc目录,根据上图,
建立了adc-core.c、adc-dev.c、adc-sysfs.c、adc-proc.c、adc.h和 gsc3280_adc.c等文件。现在先简单说下
各个文件的功能:
gsc3280_adc.c:是最底层的直接和硬件打交道的驱动文件,将在(二)中讲述。
adc-core.c:gsc3280_adc.c的上面一层,提供了ADC子系统的一些公共函数,让各个ADC驱动注册集成到
linux内核中,向驱动程序提供了注册/注销接口。将在第二篇文章中讲述。
adc-dev.c:adc-core.c再往上就到了adc-dev.c,adc-dev.c最终生成了/dev/adc设备节点,上层的应用程
序就是通过操作此文件来进行相关的读取AD转换值等操作的。定义了基本的设备文件操作函数,用户程序与ADC驱
动的接口函数,这里定义了每个ioctl命令需要调用的函数,还有open,read等。将在第二篇文章中讲述。
adc-proc.c:与proc文件系统有关,提供通过proc文件系统操作ADC。将在第二篇文章中讲述。
adc-sysfs.c:与sysfs有关,提供通过sys文件系统操作ADC。将在第二篇文章中讲述。
adc.h、adc-core.h:定义了与ADC有关的数据结构,变量和函数声明等。在使用时讲述。
二、驱动程序gsc3280_adc.c
2.1、模块初始化
本部分讲述gsc3280_adc.c文件中的程序,此即为上图中的最下部分--驱动程序,首先从模块初始化开始,程
序如下:
-
static struct platform_driver gsc3280adc_driver = {
-
.driver = {
-
.name = "adc-core",
-
.owner = THIS_MODULE,
-
},
-
.probe = gsc3280_adc_probe,
-
.remove = __devexit_p(gsc3280_adc_remove),
-
.suspend = gsc3280_adc_suspend,
-
.resume = gsc3280_adc_resume,
-
};
-
-
static int __init gsc3280_adc_init(void)
-
{
-
int ret = 0;
-
-
ret = platform_driver_register(&gsc3280adc_driver);
-
if (ret != 0)
-
DBG("!!!!!!gsc adc core register error!!!!!!\n");
-
return ret;
-
}
-
static void __exit gsc3280_adc_exit(void)
-
{
-
platform_driver_unregister(&gsc3280adc_driver);
-
}
-
module_init(gsc3280_adc_init);
-
module_exit(gsc3280_adc_exit);
-
-
MODULE_AUTHOR("Davied");
-
MODULE_DESCRIPTION("gsc3280 spi0 adc Driver");
-
MODULE_LICENSE("GPL");
-
MODULE_ALIAS("platform:gsc3280-spi0 adc");
在这里将设备定义为平台设备,驱动注册函数即为平台驱动的注册。
2.2、探测函数
接下来看下平台驱动的探测函数,程序如下:
-
static int __devinit gsc3280_adc_probe(struct platform_device *pdev)
-
{
-
int ret = 0, size = 0;
-
unsigned long rate = 0;
-
struct gsc_adc_dev *adc;
-
struct resource *mem, *ioarea;
-
-
DBG("############\n");
-
printk(KERN_INFO "GSC3280 spi0 adc probe start\n");
-
adc = kzalloc(sizeof(struct gsc_adc_dev), GFP_KERNEL);
-
if (adc == NULL) {
-
DBG("failed to allocate adc_core_dev\n");
-
return -ENOMEM;
-
}
-
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-
if (!mem) {
-
DBG("no mem resource.\n");
-
ret = -EINVAL;
-
goto err_alloc;
-
}
-
size = resource_size(mem);
-
ioarea = request_mem_region(mem->start, size, pdev->name);
-
if (!ioarea) {
-
DBG("SPI region already claimed.\n");
-
ret = -EBUSY;
-
goto err_alloc;
-
}
-
adc->regs = ioremap_nocache(mem->start, resource_size(mem));
-
if (!adc->regs) {
-
DBG("SPI region already mapped.\n");
-
ret = -ENOMEM;
-
goto err_mem;
-
}
-
DBG("probe: mapped spi0 base=%p.\n", adc->regs);
-
-
adc->clk = clk_get(NULL, "spi0");
-
if (IS_ERR(adc->clk)) {
-
DBG("failed to find watchdog clock source.\n");
-
ret = PTR_ERR(adc->clk);
-
goto err_map;
-
}
-
rate = clk_get_rate(adc->clk);
-
DBG("rate is %ld.\n", rate);
-
clk_enable(adc->clk);
-
ret = adc_sysctl(adc);
-
if (ret != 0)
-
goto err_map;
-
-
spin_lock_init(&adc->lock);
-
INIT_LIST_HEAD(&adc->device_entry);
-
strlcpy(adc->name, GSC3280_ADC_NAME, sizeof(adc->name));
-
-
mutex_lock(&gsc3280_adc_list_lock);
-
list_add(&adc->device_entry, &gsc3280_adc_list);
-
mutex_unlock(&gsc3280_adc_list_lock);
-
-
adc->adc_dev = adc_device_register(adc->name, &pdev->dev, &gsc3280_adc_ops, THIS_MODULE);
-
if (IS_ERR(adc->adc_dev)) {
-
ret = PTR_ERR(adc->adc_dev);
-
DBG("unable to register the class device\n");
-
goto err_clk;
-
}
-
-
platform_set_drvdata(pdev, adc);
-
printk(KERN_INFO "GSC3280 adc probe SUCCESS.\n");
-
DBG("############\n");
-
return 0;
-
-
err_clk:
-
clk_disable(adc->clk);
-
clk_put(adc->clk);
-
err_map:
-
iounmap(adc->regs);
-
err_mem:
-
release_mem_region(mem->start, size);
-
mem = NULL;
-
err_alloc:
-
kfree(adc);
-
printk(KERN_INFO "!!!!!!GSC3280 adc probe error!!!!!!\n");
-
return ret;
-
}
说明:
1、首先申请驱动结构体内存。
2、对资源的申请和映射,包括IO内存等。
3、使能spi0时钟。
4、配置系统控制寄存器--ret = adc_sysctl(adc)。
5、驱动结构体成员初始化。
6、adc子系统注册,此处尤为重要,将在(三)中讲述。
7、通过gsc3280_adc_list_lock和gsc3280_adc_list,使得在其他函数中也能找到1中定义的驱动结构体内存。
8、在红色部分程序中,注册了文件操作函数集gsc3280_adc_ops,在第二篇文章中会多次使用此函数,具体定
义如下:
-
static const struct adc_class_ops gsc3280_adc_ops = {
-
.convert = gsc3280AdcCon,
-
};
这里只定义一个转换函数gsc3280AdcCon,就是底层驱动程序提供的对spi0操作的函数,上层调用的AD转换动
作,最终都是由此程序来完成的。具体程序如下:
-
//return 0:date valid, other:date error
-
static int gsc3280AdcCon(unsigned short cmd)
-
{
-
struct gsc_adc_dev *adc;
-
int ret = 0, status = 0/*, cnt = 0*/;
-
-
//DBG("gscAdcCon\n");
-
mutex_lock(&gsc3280_adc_list_lock);
-
list_for_each_entry(adc, &gsc3280_adc_list, device_entry) {
-
if(strcmp(adc->name, GSC3280_ADC_NAME) == 0) {
-
status = 0;
-
break;
-
}
-
}
-
mutex_unlock(&gsc3280_adc_list_lock);
-
if (status != 0) {
-
DBG("get gsc3280 adc struct error\n");
-
return -5;
-
}
-
-
adc->cmd = cmd;
-
ret = writeSpiDate(adc); //send test cmd
-
if (ret < 0) {
-
//DBG("cmd = %x\n", adc->cmd);
-
return ret;
-
}
-
ret = readSpiDate(adc);
-
if (ret < 0) {
-
//DBG("result = %x\n", adc->result);
-
return ret;
-
}
-
if (adc->result != ((adc->cmd >> 12) | 0x8000)) {
-
DBG("cmd error\n");
-
return CMD_ERR;
-
}
-
again:
-
adc->cmd = CMD_GSC_ADC_NOP;
-
ret = writeSpiDate(adc); //send nop cmd
-
if (ret < 0) {
-
DBG("send nop cmd error\n");
-
return ret;
-
}
-
ret = readSpiDate(adc);
-
if (ret < 0) {
-
DBG("in read result = %x\n", adc->result);
-
return ret;
-
}
-
if ((adc->result & 0xf000) == 0xf000)
-
goto again;
-
if ((adc->result & 0xf000) == 0) {
-
adc->result &= 0x0fff;
-
DBG("get result success, result = %d\n", adc->result);
-
return adc->result;
-
} else {
-
DBG("get adc result error, result = %d\n", adc->result);
-
return RESULT_ERR;
-
}
-
}
使用spi读写数据函数如下:
-
//ret: 1:busy, 0:free
-
static int getSpiState(struct gsc_adc_dev *adc)
-
{
-
unsigned int time_cnt = 0;
-
-
while (readl(adc->regs + GSC_SPI_SR) & GSC_SPI_SR_BUSY) {
-
if (time_cnt++ > MAX_WAIT_CNT) {
-
DBG("spi busy, stat = %x\n", readl(adc->regs + GSC_SPI_SR));
-
return SPI_BUSY;
-
}
-
}
-
return 0;
-
}
-
-
static int writeSpiDate(struct gsc_adc_dev *adc)
-
{
-
int cnt = 0, stat = 0;
-
-
stat = getSpiState(adc);
-
if (stat != 0) {
-
DBG("in write spi date,spi is busy\n");
-
return stat;
-
}
-
//spi0 fifo can write, transmit fifo empty
-
while (!(readl(adc->regs + GSC_SPI_SR) & GSC_SPI_SR_TX_NO_FULL)) {
-
if (cnt++ > MAX_WAIT_CNT) {
-
DBG("write spi date error, stat = %x\n", readl(adc->regs + GSC_SPI_SR));
-
return WRITE_DATE_ERR;
-
}
-
}
-
writel(adc->cmd, adc->regs + GSC_SPI_DA_S);
-
return 0;
-
}
-
-
/* prepare to read data from adc */
-
static int readSpiDate(struct gsc_adc_dev *adc)
-
{
-
int cnt= 0, stat = 0;
-
-
stat = getSpiState(adc);
-
if (stat < 0) {
-
DBG("in read spi date,spi is busy\n");
-
return stat;
-
}
-
//spi0 fifo receive not empty
-
while (!(readl(adc->regs + GSC_SPI_SR) & GSC_SPI_SR_RX_N_EMPTY)) {
-
if (cnt++ > MAX_WAIT_CNT) {
-
DBG("read spi date error, spi stat = %x\n", readl(adc->regs + GSC_SPI_SR));
-
return READ_DATE_ERR;
-
}
-
}
-
adc->result = (unsigned short)readl(adc->regs + GSC_SPI_DA_S);
-
return 0;
-
}
2.3、移除函数--gsc3280_adc_remove
移除函数就是探测函数的相反过程,程序如下:
-
static int __devexit gsc3280_adc_remove(struct platform_device *pdev)
-
{
-
struct gsc_adc_dev *adc = platform_get_drvdata(pdev);
-
-
iounmap(adc->regs);
-
clk_disable(adc->clk);
-
clk_put(adc->clk);
-
adc_device_unregister(adc->adc_dev);
-
kfree(adc);
-
return 0;
-
}
三、ADC子系统核心(adc-core.c)
adc-core.c是gsc3280_adc.c的上面一层,提供了ADC子系统的一些公共函数,让各个ADC驱动注册集成到
linux内核中,向驱动程序提供了注册/注销接口。首先还是先看下模块初始化和退出函数。
3.1、模块初始化和退出函数
-
static int __init gsc_adc_init(void)
-
{
-
adc_class = class_create(THIS_MODULE, "adc");
-
if (IS_ERR(adc_class)) {
-
printk(KERN_ERR "%s: couldn't create class\n", __FILE__);
-
return PTR_ERR(adc_class);
-
}
-
//adc_class->suspend = adcSuspend;
-
//adc_class->resume = adcResume;
-
adc_dev_init();
-
adc_sysfs_init(adc_class);
-
writel(0x01, (volatile unsigned int *)0xbc04a0ac); //enable ts and adc
-
return 0;
-
}
-
static void __exit gsc_adc_exit(void)
-
{
-
adc_dev_exit();
-
class_destroy(adc_class);
-
idr_destroy(&adc_idr);
-
}
-
subsys_initcall(gsc_adc_init);
-
module_exit(gsc_adc_exit);
说明:
1、首先建立了一个设备类,在《GSC3280的ADC子系统驱动模型(三)----class的使用》中介绍。
2、对ADC子系统中的dev进行初始化,第二篇文章讲述。
3、对ADC子系统中的sysfs进行初始化,第二篇文章讲述。
4、注意:此处的初始化宏使用的是subsys_initcall,优先级高于module_init(),即subsys_initcall先于module_init()执行。
5、退出函数就是初始化函数的相反过程。
3.2、ADC子系统注册和注销函数
现在就来看下2.2中涉及到的ADC子系统注册函数。程序如下:
-
struct class *adc_class;
-
static DEFINE_IDR(adc_idr);
-
static DEFINE_MUTEX(adc_idr_lock);
-
-
static void adc_device_release(struct device *dev)
-
{
-
struct adc_core_dev *adc = to_adc_device(dev);
-
-
mutex_lock(&adc_idr_lock);
-
idr_remove(&adc_idr, adc->id);
-
mutex_unlock(&adc_idr_lock);
-
kfree(adc);
-
}
-
-
/**
-
* adc_device_register - register w/ ADC class
-
* @dev: the device to register
-
*
-
* adc_device_unregister() must be called when the class device is no
-
* longer needed.
-
*
-
* Returns the pointer to the new struct class device.
-
*/
-
struct adc_core_dev *adc_device_register(const char *name, struct device *dev,
-
const struct adc_class_ops *ops,
-
struct module *owner)
-
{
-
struct adc_core_dev *adc;
-
int id, err;
-
-
if (idr_pre_get(&adc_idr, GFP_KERNEL) == 0) {
-
err = -ENOMEM;
-
goto exit;
-
}
-
mutex_lock(&adc_idr_lock);
-
err = idr_get_new(&adc_idr, NULL, &id);
-
mutex_unlock(&adc_idr_lock);
-
if (err < 0)
-
goto exit;
-
id = id & MAX_ID_MASK;
-
-
adc= kzalloc(sizeof(struct adc_core_dev), GFP_KERNEL);
-
if (adc == NULL) {
-
err = -ENOMEM;
-
goto exit_idr;
-
}
-
-
adc->id = id;
-
adc->ops = ops;
-
adc->owner = owner;
-
adc->dev.parent = dev;
-
adc->dev.class = adc_class;
-
adc->dev.release = adc_device_release;
-
-
mutex_init(&adc->ops_lock);
-
strlcpy(adc->name, name, ADC_CORE_NAME_SIZE);
-
dev_set_name(&adc->dev, "adc%d", id);
-
-
adc_dev_prepare(adc);
-
-
err = device_register(&adc->dev);
-
if (err) {
-
put_device(&adc->dev);
-
goto exit_kfree;
-
}
-
-
#ifdef CONFIG_TOUCHSCREEN_GSC3280
-
err = adc_ts_add_dev(adc);
-
if (err < 0)
-
DBG("adc ts add dev error\n");
-
#endif
-
-
adc_dev_add_device(adc);
-
adc_sysfs_add_device(adc);
-
adc_proc_add_device(adc);
-
-
dev_info(dev, "adc core: registered %s as %s\n", adc->name, dev_name(&adc->dev));
-
return adc;
-
-
exit_kfree:
-
kfree(adc);
-
exit_idr:
-
mutex_lock(&adc_idr_lock);
-
idr_remove(&adc_idr, id);
-
mutex_unlock(&adc_idr_lock);
-
exit:
-
dev_err(dev, "adc core: unable to register %s, err = %d\n", name, err);
-
return ERR_PTR(err);
-
}
-
EXPORT_SYMBOL_GPL(adc_device_register);
-
-
/**
-
* adc_device_unregister - removes the previously registered ADC class device
-
*
-
* @adc: the ADC class device to destroy
-
*/
-
void adc_device_unregister(struct adc_core_dev *adc)
-
{
-
if (get_device(&adc->dev) != NULL) {
-
mutex_lock(&adc->ops_lock);
-
adc_sysfs_del_device(adc);
-
adc_dev_del_device(adc);
-
adc_proc_del_device(adc);
-
device_unregister(&adc->dev);
-
adc->ops = NULL;
-
mutex_unlock(&adc->ops_lock);
-
put_device(&adc->dev);
-
}
-
}
-
EXPORT_SYMBOL_GPL(adc_device_unregister);
说明:
1、首先使用idr机制获取id。
2、申请结构体内存,初始化成员变量。
3、注册device,在《GSC3280的ADC子系统驱动模型(三)----class的使用》中介绍。
4、增加dev、proc和sysfs设备,第二篇文章中讲述。
5、注销函数是注册函数的相反过程。
6、释放函数就是移除idr,释放结构体内存。
四、Kconfig和Makefile编写
首先我们在Linux内核源码目录下的/driver/spi/adc目录下创建Kconfig和Makefile文件。
4.1、Kconfig
Kconfig程序如下:
-
#
-
# Sensor device configuration
-
# add by hdw,in order to use adc
-
#
-
-
menuconfig SPI0_ADC
-
bool "Adc Hardware support"
-
help
-
GSC3280 Adc Hardware support.
-
-
-
if SPI0_ADC
-
-
config GSC_SPI0_ADC_CORE
-
tristate "support adc core"
-
default SPI0_ADC
-
help
-
If you say yes to this option, support will be included gsc3280
-
adc core.
-
-
config GSC_ADC_CORE_DEBUG
-
bool "adc core debugging messages"
-
depends on GSC_SPI0_ADC_CORE
-
help
-
Say Y here if you want the GSC3280 to produce a bunch of debug
-
messages to the system log. Select this if you are having a
-
problem with GSC3280 and want to see more of what is going on.
-
-
comment "ADC interfaces"
-
-
config ADC_INTF_SYSFS
-
boolean "/sys/class/adc/adcN (sysfs)"
-
depends on SYSFS
-
default SPI0_ADC
-
help
-
Say yes here if you want to use your ADCs using sysfs interfaces,
-
/sys/class/adc/adc0 through /sys/.../adcN.
-
-
If unsure, say Y.
-
-
config ADC_SYS_DEBUG
-
bool "adc sys debugging messages"
-
depends on ADC_INTF_SYSFS
-
help
-
Say Y here if you want the adc sysfs dev to produce a bunch of debug
-
messages to the system log. Select this if you are having a
-
problem with adc core and want to see more of what is going on.
-
-
config ADC_INTF_PROC
-
boolean "/proc/driver/adc (procfs for adc0)"
-
depends on PROC_FS
-
default SPI0_ADC
-
help
-
Say yes here if you want to use your first ADC through the proc
-
interface, /proc/driver/adc. Other ADCs will not be available
-
through that API.
-
-
If unsure, say Y.
-
-
config ADC_PROC_DEBUG
-
bool "adc proc debugging messages"
-
depends on ADC_INTF_PROC
-
help
-
Say Y here if you want the adc proc dev to produce a bunch of debug
-
messages to the system log. Select this if you are having a
-
problem with adc core and want to see more of what is going on.
-
-
config ADC_INTF_DEV
-
boolean "/dev/adcN (character devices)"
-
default SPI0_ADC
-
help
-
Say yes here if you want to use your ADCs using the /dev
-
interfaces, which "udev" sets up as /dev/adc0 through
-
/dev/adcN.
-
-
You may want to set up a symbolic link so one of these
-
can be accessed as /dev/adc, which is a name
-
expected by "hwclock" and some other programs. Recent
-
versions of "udev" are known to set up the symlink for you.
-
-
If unsure, say Y.
-
-
config ADC_DEV_DEBUG
-
bool "adc dev debugging messages"
-
depends on ADC_INTF_DEV
-
help
-
Say Y here if you want the adc dev to produce a bunch of debug
-
messages to the system log. Select this if you are having a
-
problem with adc core and want to see more of what is going on.
-
-
-
comment "ADC devices"
-
-
config GSC3280_ADC
-
tristate "support gsc3280 adc"
-
depends on GSC_SPI0_ADC_CORE
-
default SPI0_ADC
-
help
-
If you say yes to this option, support will be included gsc3280
-
adc convert and touchscreen.
-
-
config GSC3280_ADC_DEBUG
-
bool "GSC3280 adc debugging messages"
-
depends on GSC3280_ADC
-
help
-
Say Y here if you want the GSC3280 adc to produce a bunch of debug
-
messages to the system log. Select this if you are having a
-
problem with GSC3280 and want to see more of what is going on.
-
-
-
endif
在上一层的Kconfig,也就是Linux内核源码目录下的/driver/spi下的Kconfig增加如下内容:
-
# add by hdw
-
comment "gsc3280 spi0 adc"
-
source drivers/spi/adc/Kconfig
这样在配置选项中,就可以配置ADC子系统的内容了。
4.2、Makefile
类似Kconfig,在Linux内核源码目录/driver/spi/adc目录下创建Makefile文件,具体内容如下:
-
#
-
# Makefile for the i2c bus drivers.
-
#
-
-
obj-$(CONFIG_GSC_SPI0_ADC_CORE) += adc-core.o
-
obj-$(CONFIG_ADC_INTF_DEV) += adc-dev.o
-
obj-$(CONFIG_ADC_INTF_PROC) += adc-proc.o
-
obj-$(CONFIG_ADC_INTF_SYSFS) += adc-sysfs.o
-
-
obj-$(CONFIG_GSC3280_ADC) += gsc3280_adc.o
-
-
ccflags-$(CONFIG_GSC_ADC_DEV_DEBUG) += -DGSC3280_ADC_DEV_DEBUG
在上一层的Makefile,也就是Linux内核源码目录下的/driver/spi下的Makefile增加如下内容:
-
#add by hdw
-
obj-y += adc/
阅读(764) | 评论(0) | 转发(0) |