S3C2440驅動簡析——觸摸屏驅動

    因困於雜事,博客荒廢將近半個月,此時此刻重提筆墨,繼續記錄鄙人學習之歷程。

    本文將簡要分析2440的觸摸屏驅動,其驅動程序內核自帶。在瀏覽本文之前,如果對Linux 驅動的input 子系統沒有認識的話,請先回頭參考鄙人之前的博文《input子系統http://blog.csdn.net/jarvis_xian/article/details/6552579

 

    事不宜遲,馬上進入本文的正題。有經驗的朋友都知道,看驅動,先找入口、出口!

static int __init s3c2410ts_init(void)
{
	return platform_driver_register(&s3c_ts_driver);//註冊platform驅動,注意參數s3c_ts_driver
}

static void __exit s3c2410ts_exit(void)
{
	platform_driver_unregister(&s3c_ts_driver);   //註銷platform驅動
}

platform_driver_register 的參數s3c_ts_driver 定義如下

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),
};

在上述結構體中,我們應該把精力放在.probe、.remove和.pm上

探討probe 函數前,我們先看一下結構體ts 的定義,其統籌了整個驅動所用到的資源

struct s3c2410ts {
	struct s3c_adc_client *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;
};


s3c2410ts_probe代碼如下:

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;   //把platform的設備掛到ts 結構體

	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);   //使能ts 時鐘
	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));   //物理地址映射到虛擬地址
	if (ts.io == NULL) {
		dev_err(dev, "cannot map registers\n");
		ret = -ENOMEM;
		goto err_clk;
	}

	/* inititalise the gpio */
	if (info->cfg_gpio)
		info->cfg_gpio(to_platform_device(ts.dev));

	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 */
	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;
	}

	ts.input = input_dev;   //把input_dev 掛接到ts 結構體
	ts.input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); //設置事件類型
	ts.input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
	input_set_abs_params(ts.input, ABS_X, 0, 0x3FF, 0, 0);   //設置邊界條件等
	input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0);

	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);   //初始化完畢後,註冊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;
}
 
相對於probe,remove的代碼如下
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;
}
 
至於上文提及的.pm,實則也是一個結構體,如下
static struct dev_pm_ops s3c_ts_pmops = {
	.suspend	= s3c2410ts_suspend,
	.resume		= s3c2410ts_resume,
};
包含了suspend 和resume 兩個操作。
 
s3c2410ts_suspend函數代碼:
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;
}
 
s3c2410ts_resume函數代碼:
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 irqreturn_t stylus_irq(int irq, void *dev_id)
{
	unsigned long data0;
	unsigned long data1;
	bool down;

	data0 = readl(ts.io + S3C2410_ADCDAT0);   //讀取X、Y軸座標
	data1 = readl(ts.io + S3C2410_ADCDAT1);

	down = get_down(data0, data1);   //關於get_down函數查看下文

	/* 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)
		s3c_adc_start(ts.client, 0, 1 << ts.shift);   //開始ADC轉換
	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;
}
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));
}
 
touch_timer_fire函數將觸摸屏的數據報告給內核
static void touch_timer_fire(unsigned long data)
{
	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);

	if (down) {
		if (ts.count == (1 << ts.shift)) {
			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);

			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);
			input_sync(ts.input);   //結束同步

			ts.xp = 0;
			ts.yp = 0;
			ts.count = 0;
		}

		s3c_adc_start(ts.client, 0, 1 << ts.shift);
	} else {
		ts.xp = 0;
		ts.yp = 0;
		ts.count = 0;

		input_report_key(ts.input, BTN_TOUCH, 0);
		input_sync(ts.input);

		writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);
	}
}
 
    
    最後,我們可以看到觸摸屏驅動是基於platform + input子系統 搭建起來的,正如搭積木一樣,只要理解了每一個系統、部件的特點和接口等等,再稍加努力消化,就可把各式簡單驅動程序的架構辨清,繼而爲自己編寫驅動或者修改驅動打下了一個良好的基礎。以上僅是菜鳥之愚見,還請前輩多多指教!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章