1. 程式人生 > >驅動之路四------adc驅動(input裝置)

驅動之路四------adc驅動(input裝置)

開發板:smdk6410

開發環境:Linux

突然想起一點,寫這些驅動,核心需要配成支援搶佔才行。

前面的部落格已經將其它的基本知識都解釋了,這裡也就不過多的闡述了,咱就直接寫程式碼吧

這次寫的是adc驅動,將其做為輸入裝置進行使用,

先寫標頭檔案,s3c_adc.h

#ifndef __ADC_H
#define __ADC_H

#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/input.h>

struct adc_info {
	char name[32];
	int user;
	int status;
	void __iomem *v;
	struct input_dev *dev;
	struct clk *clk;
	struct timer_list timer;
	int irq;
	irqreturn_t (*handle)(int no, void *data);
};

#define S3C_PA_ADC	0x7e00b000
#define S3C_SZ_ADC	SZ_4K

#define ADCCON 		0x000
#define ADCTSC 		0x004
#define ADCDLY 		0x008
#define ADCDAT0 	0x00C
#define ADCDAT1 	0x010
#define ADCUPDN 	0x014
#define ADCCLRINT 	0x018
#define ADCCLRINTPNDNUP	0x020 

#define S3C_IRQ_ADC_S	IRQ_ADC	
#define S3C_IRQ_ADC_E	IRQ_ADC


#endif

標頭檔案主要即使裝置資訊結構體和相關的巨集定義,

現在寫裝置檔案,

#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>

#include "s3c_adc.h"

void b_release(struct device *dev)
{
	printk("Device is released\n");
}
//資源也是兩類,MEM一類,IRQ一類
struct resource b_res[] = {
	[0] = {
		.start = S3C_PA_ADC,
		.end = S3C_PA_ADC + S3C_SZ_ADC - 1,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = S3C_IRQ_ADC_S,
		.end = S3C_IRQ_ADC_E,
		.flags = IORESOURCE_IRQ,
	}
};

struct platform_device dev = {
	.name = "s3c-my-adc",
	.id = -1,
	.num_resources = ARRAY_SIZE(b_res),
	.resource = b_res,
	.dev = {
		.release = b_release,
	}
};

static __init int module_test_init(void)
{
	return platform_device_register(&dev);
}

static __exit void module_test_exit(void)
{
	platform_device_unregister(&dev);
}

module_init(module_test_init);
module_exit(module_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Musesea");
MODULE_VERSION("1.0");
MODULE_DESCRIPTION("Test for module");

看過前幾篇驅動的應該都直到,套路比較固定,只要將其中的很少的東西修改以下就行,就不詳細說每一步的功能了,

下面寫驅動檔案,這才是大頭,

#include <linux/init.h>
#include <linux/module.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/clk.h>
#include <linux/clkdev.h>
#include <linux/timer.h>

#include "s3c_adc.h"

void start_adc(struct adc_info *a)
{
	u32 tmp;

	tmp = readl(a->v + ADCCON);
	tmp |= 1;
	writel(tmp, a->v + ADCCON);
}
//這個函式執行在中斷上下文,函式體內可不能含有可睡眠的函式
void do_timer(unsigned long data)
{
	struct adc_info *a;
	
	a = (struct adc_info *)data;

	//中斷上下文
	start_adc(a);
	mod_timer(&a->timer, jiffies + HZ / 2);
}

int get_adc(struct adc_info *a)
{
	u32 tmp;

	tmp = readl(a->v + ADCDAT0);
	tmp = tmp & 0xfff;
	return tmp;
}

irqreturn_t do_adc(int no, void *data)
{
	struct adc_info *a = data;
	int adc_val;

	//獲取adc轉換的結果
	adc_val = get_adc(a);


	input_report_abs(a->dev, ABS_X, adc_val);
	input_sync(a->dev);

	//清中斷
	writel(1, a->v + ADCCLRINT);

	return IRQ_HANDLED;
}

void s3c_adc_exit(struct adc_info *a)
{
	printk("Driver is release.\n");
}

void s3c_adc_init(struct adc_info *a)
{
	u32 tmp;

	//tmp = readl(a->v + ADCCON);
	tmp = (1 << 16) | (1 << 14) | (21 << 6);
	writel(tmp, a->v + ADCCON);
}

int b_probe(struct platform_device *pdev)
{
	struct resource *a_res, *irq_res;
	struct adc_info *adc;
	int ret;

	//1.申請資源
	a_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if(!a_res || !irq_res)
		return -EBUSY;

	//2.分配adc_info
	adc = kzalloc(sizeof(struct adc_info), GFP_KERNEL);
	if(!adc)
		return -ENOMEM;

	//3.ioremap
	adc->v = ioremap(a_res->start, a_res->end - a_res->start + 1);
	if(!adc->v)
	{
		ret = -ENOMEM;
		goto remap_error;
	}

	//申請裝置結構體
	adc->dev = input_allocate_device();
	if(!adc->dev)
	{
		ret = -ENOMEM;
		goto input_allocate_device_error;
	}

	adc->dev->name = pdev->name;
	adc->dev->uniq = "20131113";
	adc->dev->phys = "/dev/eventx";
	adc->dev->id.bustype = BUS_HOST;
	adc->dev->id.vendor = 110;
	adc->dev->id.product = 120;
	adc->dev->id.version = 119;

	//4.設定該裝置要支援的事件型別
	set_bit(EV_SYN, adc->dev->evbit);
	set_bit(EV_ABS, adc->dev->evbit);
	
	//使該裝置支援絕對的x事件
        //寫絕對事件時不要使用set_bit,使用核心給出的下列函式
        input_set_abs_params(adc->dev, ABS_X, 0, 4095, 0, 0);

	//5.註冊input裝置
	ret = input_register_device(adc->dev);
	if(ret)
		goto input_register_device_error;
	//將adc儲存到pdev中
	platform_set_drvdata(pdev, adc);
	
	//6.開啟clock;注意在初始化adc之前一定要寫開啟時鐘;只要使用時鐘的裝置,在初始化之前都要先開啟時鐘
	adc->clk = clk_get(NULL, "adc");	
	clk_enable(adc->clk);

	//7.初始化adc
	//sprintf(adc->name, "adc");
	adc->user = 0;
	adc->irq = irq_res->start;
	adc->handle = do_adc;

	//涉及時鐘的硬體在初始化之前一定要確認時鐘開啟
	s3c_adc_init(adc);

	//8.申請中斷(adc)
	ret = request_irq(adc->irq, adc->handle, 0, pdev->name, adc);
	if(ret){

		goto request_irq_error;
	}

	//9.建立一個adc->timer_list,定時時間為0.5s,處理函式為do_adc->timer
	setup_timer(&adc->timer, do_timer, (unsigned long)adc);
	mod_timer(&adc->timer, jiffies + HZ / 2);//這裡不要用0.5*HZ,核心是不支援浮點數的,也可以寫成(HZ>>1)

	return 0;

	free_irq(adc->irq, adc);
request_irq_error:
	input_unregister_device(adc->dev);
input_register_device_error:
	input_free_device(adc->dev);
input_allocate_device_error:
	iounmap(adc->v);
remap_error:
	kfree(adc);
	return ret;
}

int b_remove(struct platform_device *pdev)
{
	struct adc_info *adc;

	adc = platform_get_drvdata(pdev);

	del_timer_sync(&adc->timer);
	free_irq(adc->irq, adc);
	s3c_adc_exit(adc);

	//clock的反操作
	clk_disable(adc->clk);
	clk_put(adc->clk);

	input_free_device(adc->dev);
	iounmap(adc->v);
	kfree(adc);

	return 0;
}

struct platform_driver drv = {
	.driver = {
		.name = "s3c-my-adc",
	},
	.probe = b_probe,
	.remove = b_remove,
};

static __init int module_test_init(void)
{
	return platform_driver_register(&drv);
}

static __exit void module_test_exit(void)
{
	platform_driver_unregister(&drv);
}

module_init(module_test_init);
module_exit(module_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Musesea");
MODULE_VERSION("1.0");
MODULE_DESCRIPTION("Test for module");

應該注意的地方在程式碼中做了標註了,至此又一個驅動搞完了。

還是那句話,大家若是發現有什麼問題,一定要告訴我,大家一起學習了。