一、芯片和驱动架构总体介绍
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子系统架构关系图如下:![](https://i-blog.csdnimg.cn/blog_migrate/d95085f044acb7ee3568add4a61fe354.png)
由于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<apple_guet@126.com>");
- 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
点击(此处)折叠或打开
- #add by hdw
- obj-y += adc/