字符设备驱动实例(LED、按键、input输入子系统)

目录

本章目标

一、LED驱动

二、基于中断的简单按键驱动

三、基于输入子系统的按键驱动


本章目标


        本章综合前面的知识,实现了嵌入式系统的常见外设驱动,包括 LED、按键、ADC、PWM和RTC。本章从工程的角度、实用的角度探讨了某些驱动的实现。比如LED 只是编写了设备树节点,设备就能被正常驱动,按键驱动则分别讨论了基于中断的和基于输入子系统的,还特别讨论了按键的消抖处理。不仅如此,本章还引入了一些新的知识,比如内核统一 GPIO 接口、时钟子系统、pinctrl 子系统等。本章虽然叫“字符设备驱动实例”,但是有些设备并没有实现为字符设备,而是通过 sysfs 文件系统接口来操作的。
 

一、LED驱动


        经常听到的一句话“无招胜有招”是用来形容武林人士的武术修炼的境界已经达到了最高,类似的还有“无声胜有声”、“大音希声,大象希形”等。其实对于驱动也是一样的道理,如果要实现一个设备的驱动,而不必大动干戈,或者连一行驱动代码都不用写,那是不是也意味着驱动开发者的境界达到最高了呢。听起来好像是天方夜谭,但这并不是不可实现的,因为全世界的内核开发者非常热心,只要是能写的驱动,他们基本都已经写了。我们如果能够善于站在这些巨人的肩膀上,那么我们就会工作得更轻松。接下来要讨论的LED驱动就要利用内核开发者已经写好的驱动来实现我们想要的功能。在你动手写一个驱动之前,应该先看看内核是否已经实现了这个驱动。如果是,那么这会极大地提高我们的工作效率,毕竟不敲一行代码就能拿到薪水是每一个程序员都追求的终极目标,但是这个千万不能告诉老板。
        我们的LED 是基于 GPIO 的,为此,内核有两个对应的驱动程序,分别是 GPIO驱动和LED驱动,基于GPIO的LED 驱动调用了GPIO 驱动导出的函数,这一节我们并不关心GPIO的驱动(后面会有详细的说明)。关于 LED 驱动,内核文档Documentation/leds/leds-class.txt 有简单的描述,它实现了一个leds 类,通过 sysfs 的接口对 LED进行控制所以它并没有使用字符设备驱动的框架,严格来说,这一节内容和本章的标题是不符合的。

        驱动的实现代码请参见 drivers/leds/leds-gpio.c

/*
 * LEDs driver for GPIOs
 *
 * Copyright (C) 2007 8D Technologies inc.
 * Raphael Assenat <raph@8d.com>
 * Copyright (C) 2008 Freescale Semiconductor, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/leds.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/err.h>

struct gpio_led_data {
	struct led_classdev cdev;
	unsigned gpio;
	struct work_struct work;
	u8 new_level;
	u8 can_sleep;
	u8 active_low;
	u8 blinking;
	int (*platform_gpio_blink_set)(unsigned gpio, int state,
			unsigned long *delay_on, unsigned long *delay_off);
};

static void gpio_led_work(struct work_struct *work)
{
	struct gpio_led_data	*led_dat =
		container_of(work, struct gpio_led_data, work);

	if (led_dat->blinking) {
		led_dat->platform_gpio_blink_set(led_dat->gpio,
						 led_dat->new_level,
						 NULL, NULL);
		led_dat->blinking = 0;
	} else
		gpio_set_value_cansleep(led_dat->gpio, led_dat->new_level);
}

static void gpio_led_set(struct led_classdev *led_cdev,
	enum led_brightness value)
{
	struct gpio_led_data *led_dat =
		container_of(led_cdev, struct gpio_led_data, cdev);
	int level;

	if (value == LED_OFF)
		level = 0;
	else
		level = 1;

	if (led_dat->active_low)
		level = !level;

	/* Setting GPIOs with I2C/etc requires a task context, and we don't
	 * seem to have a reliable way to know if we're already in one; so
	 * let's just assume the worst.
	 */
	if (led_dat->can_sleep) {
		led_dat->new_level = level;
		schedule_work(&led_dat->work);
	} else {
		if (led_dat->blinking) {
			led_dat->platform_gpio_blink_set(led_dat->gpio, level,
							 NULL, NULL);
			led_dat->blinking = 0;
		} else
			gpio_set_value(led_dat->gpio, level);
	}
}

static int gpio_blink_set(struct led_classdev *led_cdev,
	unsigned long *delay_on, unsigned long *delay_off)
{
	struct gpio_led_data *led_dat =
		container_of(led_cdev, struct gpio_led_data, cdev);

	led_dat->blinking = 1;
	return led_dat->platform_gpio_blink_set(led_dat->gpio, GPIO_LED_BLINK,
						delay_on, delay_off);
}

static int create_gpio_led(const struct gpio_led *template,
	struct gpio_led_data *led_dat, struct device *parent,
	int (*blink_set)(unsigned, int, unsigned long *, unsigned long *))
{
	int ret, state;

	led_dat->gpio = -1;

	/* skip leds that aren't available */
	if (!gpio_is_valid(template->gpio)) {
		dev_info(parent, "Skipping unavailable LED gpio %d (%s)\n",
				template->gpio, template->name);
		return 0;
	}

	ret = devm_gpio_request(parent, template->gpio, template->name);
	if (ret < 0)
		return ret;

	led_dat->cdev.name = template->name;
	led_dat->cdev.default_trigger = template->default_trigger;
	led_dat->gpio = template->gpio;
	led_dat->can_sleep = gpio_cansleep(template->gpio);
	led_dat->active_low = template->active_low;
	led_dat->blinking = 0;
	if (blink_set) {
		led_dat->platform_gpio_blink_set = blink_set;
		led_dat->cdev.blink_set = gpio_blink_set;
	}
	led_dat->cdev.brightness_set = gpio_led_set;
	if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP)
		state = !!gpio_get_value_cansleep(led_dat->gpio) ^ led_dat->active_low;
	else
		state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
	led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
	if (!template->retain_state_suspended)
		led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;

	ret = gpio_direction_output(led_dat->gpio, led_dat->active_low ^ state);
	if (ret < 0)
		return ret;

	INIT_WORK(&led_dat->work, gpio_led_work);

	ret = led_classdev_register(parent, &led_dat->cdev);
	if (ret < 0)
		return ret;

	return 0;
}

static void delete_gpio_led(struct gpio_led_data *led)
{
	if (!gpio_is_valid(led->gpio))
		return;
	led_classdev_unregister(&led->cdev);
	cancel_work_sync(&led->work);
}

struct gpio_leds_priv {
	int num_leds;
	struct gpio_led_data leds[];
};

static inline int sizeof_gpio_leds_priv(int num_leds)
{
	return sizeof(struct gpio_leds_priv) +
		(sizeof(struct gpio_led_data) * num_leds);
}

/* Code to create from OpenFirmware platform devices */
#ifdef CONFIG_OF_GPIO
static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node, *child;
	struct gpio_leds_priv *priv;
	int count, ret;

	/* count LEDs in this device, so we know how much to allocate */
	count = of_get_available_child_count(np);
	if (!count)
		return ERR_PTR(-ENODEV);

	for_each_available_child_of_node(np, child)
		if (of_get_gpio(child, 0) == -EPROBE_DEFER)
			return ERR_PTR(-EPROBE_DEFER);

	priv = devm_kzalloc(&pdev->dev, sizeof_gpio_leds_priv(count),
			GFP_KERNEL);
	if (!priv)
		return ERR_PTR(-ENOMEM);

	for_each_available_child_of_node(np, child) {
		struct gpio_led led = {};
		enum of_gpio_flags flags;
		const char *state;

		led.gpio = of_get_gpio_flags(child, 0, &flags);
		led.active_low = flags & OF_GPIO_ACTIVE_LOW;
		led.name = of_get_property(child, "label", NULL) ? : child->name;
		led.default_trigger =
			of_get_property(child, "linux,default-trigger", NULL);
		state = of_get_property(child, "default-state", NULL);
		if (state) {
			if (!strcmp(state, "keep"))
				led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
			else if (!strcmp(state, "on"))
				led.default_state = LEDS_GPIO_DEFSTATE_ON;
			else
				led.default_state = LEDS_GPIO_DEFSTATE_OFF;
		}

		ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],
				      &pdev->dev, NULL);
		if (ret < 0) {
			of_node_put(child);
			goto err;
		}
	}

	return priv;

err:
	for (count = priv->num_leds - 2; count >= 0; count--)
		delete_gpio_led(&priv->leds[count]);
	return ERR_PTR(-ENODEV);
}

static const struct of_device_id of_gpio_leds_match[] = {
	{ .compatible = "gpio-leds", },
	{},
};
#else /* CONFIG_OF_GPIO */
static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev)
{
	return ERR_PTR(-ENODEV);
}
#endif /* CONFIG_OF_GPIO */


static int gpio_led_probe(struct platform_device *pdev)
{
	struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
	struct gpio_leds_priv *priv;
	int i, ret = 0;


	if (pdata && pdata->num_leds) {
		priv = devm_kzalloc(&pdev->dev,
				sizeof_gpio_leds_priv(pdata->num_leds),
					GFP_KERNEL);
		if (!priv)
			return -ENOMEM;

		priv->num_leds = pdata->num_leds;
		for (i = 0; i < priv->num_leds; i++) {
			ret = create_gpio_led(&pdata->leds[i],
					      &priv->leds[i],
					      &pdev->dev, pdata->gpio_blink_set);
			if (ret < 0) {
				/* On failure: unwind the led creations */
				for (i = i - 1; i >= 0; i--)
					delete_gpio_led(&priv->leds[i]);
				return ret;
			}
		}
	} else {
		priv = gpio_leds_create_of(pdev);
		if (IS_ERR(priv))
			return PTR_ERR(priv);
	}

	platform_set_drvdata(pdev, priv);

	return 0;
}

static int gpio_led_remove(struct platform_device *pdev)
{
	struct gpio_leds_priv *priv = platform_get_drvdata(pdev);
	int i;

	for (i = 0; i < priv->num_leds; i++)
		delete_gpio_led(&priv->leds[i]);

	return 0;
}

static struct platform_driver gpio_led_driver = {
	.probe		= gpio_led_probe,
	.remove		= gpio_led_remove,
	.driver		= {
		.name	= "leds-gpio",
		.owner	= THIS_MODULE,
		.of_match_table = of_match_ptr(of_gpio_leds_match),
	},
};

module_platform_driver(gpio_led_driver);

MODULE_AUTHOR("Raphael Assenat <raph@8d.com>, Trent Piepho <tpiepho@freescale.com>");
MODULE_DESCRIPTION("GPIO LED driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:leds-gpio");


        既然驱动已经实现了,那么我们要怎么来让它工作起来呢?首先要配置内核,确保驱动被选配了。在内核源码下运行 make  ARCH=arm menuconfig 命令,按照下面的选项进行选择。


驱动选配好后,保存配置,并使用下面的命令重新编译内核,然后再复制到 TFTP服务器指定的目录下。

make ARCH=arm uImage


        驱动配置好之后,就应该在设备树中添加设备节点,设备节点的编写方法请参考内核文档Documentation/devicetree/bindings/leds/leds-gpio.txt(在编写设备节点之前都在内核文档中找找对应的说明,这是一个良好的习惯)。修改 arch/arm/boot/dts/exynos4412-fs4412.dts,删除之前添加的 LED 设备节点,添加下面的设备节点。

	leds{
		compatible = "gpio-leds";
		
		led2 {
			label = "led2";
			gpios = <&gpx2 7 0>;
			default-state = "off";
		};
		
		led3 {
			label = "led3";
			gpios = <&gpx1 0 0>;
			default-state = "off";
		};

		led4 {
			label = "led4";
			gpios = <&gpf3 4 0>;
			default-state = "off";
		};

		led5 {
			label = "led5";
		    gpios = <&gpf3 5 0>;
			default-state = "off";
		};
	};

        compatible 属性为gpio-leds,可以和 LED驱动匹配。每个led 节点中的label是出现在sys目录下的子目录名字。gpios 则指定了该LED所连接的GPIO口,第三个值为0表示高电平点亮 LED灯,为1则表示低电平点亮 LED 灯。default-state 属性的值为off,则表示默认情况下 LED 灯是熄灭的,为 on 则默认点亮。修改好设备树源文件后,使用下面的命令编译设备树,然后复制到指定目录。

重新启动开发板,使用下面的命令可以看到对应的设备目录。

led2、led3、led4、led5 分别对应了 4个LED 灯,在每个目录下都有一个 brightness文件,通过读取该文件可以获取 LED 灯的当前亮度,通过写该文件可以修改 LED 灯的亮度。因为这些 LED 灯饰连接在 GPIO 端口上面,所以亮度只有 0和1,0表示熄灭,1表示点亮,命令如下。

当然,也可以编写一个应用程序来控制 LED 灯的亮灭,应用层测试代码如下

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>

#include <sys/stat.h>
#include <sys/types.h>

#define LED_DEV_PATH	"/sys/class/leds/led%d/brightness"
#define ON		1
#define OFF		0

int fs4412_set_led(unsigned int lednum, unsigned int mode)
{
	int fd;
	int ret;
	char devpath[128];
	char *on = "1\n";
	char *off = "0\n";
	char *m = NULL;

	snprintf(devpath, sizeof(devpath), LED_DEV_PATH, lednum);
	fd = open(devpath, O_WRONLY);
	if (fd == -1) {
		perror("fsled->open");
		return -1;
	}

	if (mode == ON)
		m = on;
	else
		m = off;

	ret = write(fd, m, strlen(m));
	if (ret == -1) {
		perror("fsled->write");
		close(fd);
		return -1;
	}

	close(fd);
	return 0;
}

int main(int argc, char *argv[])
{
	unsigned int lednum = 2;

	while (1) {
		fs4412_set_led(lednum, ON);
		usleep(500000);
		fs4412_set_led(lednum, OFF);
		usleep(500000);

		lednum++;
		if (lednum > 5)
			lednum = 2;
	}
}

 效果是一个流水灯试试资源绑定功能,成功的话大家后面可以下载看一看。


        代码比较简单,这里不再进行说明。需要注意的是,对sys目录下的文件操作要注意文件位置指针,简单的方法就是每次重新打开,使用后再关闭,编译和测试的命令如下4个LED灯会向前面的例子一样依次闪烁。我用了我的便捷小脚本,经过30多个驱动实验的打磨已经很好用了。后面有时间把内核编译和设备树编译用脚本管理起来,做个小sdk。(悄悄滴:看看能不能卖给华清哈哈哈哈)


二、基于中断的简单按键驱动


在 FS4412上面有三个按键,其相关的原理图如图所示。


        K2和K3可以用作一般按键输入,K4 用于电源管理。进一步结合核心板原理图可知,K2 和K3 分别接到了 GPXL.和 GPX1.2 管脚上,并且这两个管脚还可以当外部中断输入管脚,断号分别是 EINT9和 EINT10。K2和K3 是常开的按键开关,所以这两个管脚平时都处于高电平,当按下按键后,管脚被直接接地,为低电平。也就是说在按键按下的瞬间会产生一个下降沿,在按键松开的时候会产生一个上升沿。我们可以将管脚设置为下降沿触发,从而能及时响应用户的按键输入,也可以设置为双沿触发,这样在按键按下和抬起的时候都会产生中断。

        接下来我们首先在设备树中添加节点,为了要描述这两个管脚以中断方式工作,我们需要参考内核文档 Documentation/devicetree/bindings/interrupt-controller/interruptstxt,该文档中举例说明了中断属性该如何设置。

interrupt-parent = <&intcl>;
interrupts=<5 0>,<6 0>;


        interrupt-parent 指定了中断线所连接的中断控制器节点,interrupts 的值如果有两个cell,那么第一个cell是中断线在中断控制器中的索引,第二个 cell 指的是中断触发方式,0表示没指定。
        为了给出按键设备的节点,我们需要查看其中断控制器节点的内容。因为它们是接在GPX1这组管脚上的,根据gpx1 关键字我们在arch/arm/boot/dts/exynos4x12-pinctrl.dtsi文件中可以看到其定义。

这也解释了为什么前面我们设置led的设备树时没指定物理地址只说明了GPIO的名字依旧能找到并控制led因为其它的文件中已经定义好了。

        由此可见,gpx1 确实是一个中断控制器,并且使用的是两个 cell,它总共有 8根中断线,刚好对应管脚GPX1.0到GPX1.7。我们使用的管脚是 GPX1.1和GPX1.2,那么索引自然就是1和2。这也可以通过 gpxl 的父节点 gic来确定,在 Documentation/devicetreebindings/arm/gic.txt内核文档中描述了 interrupts 的三个cell的含义,第一个cell是0表的是SPI中断;第二个 cell是 SPI的中断号:第三个 cell是中断触发方式,为0表示没指定。查看 Exynos4412 芯片手册的中断控制器部分,如图所示,我们可以看到EINT9和EINT10中断对应的SPI中断号刚好是25和26。

有了这些信息后,我们就可以给出按键的设备节点,内容如下,
 

keys {
    compatible="fs4412,fskey";
    interrupt-parent = <&gpx1>;
    interrupts=<1 2>,<2 2>;
};

        interrupts 属性的第二个 cell为2 表示下降沿发。将上面的代码添加到设备树文件arch/arm/boot/dts/exynos4412-fs4412.dts 中,并重新编译设备树,然后复制到TFTP 服务器指定的目录下。



        结合前面中断编程和 Linux 设备模型的知识,我们很容易写出这个简单的按键驱动代码如下

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>

#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>

#include <linux/of.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>

struct resource *key2_res;
struct resource *key3_res;

static irqreturn_t fskey_handler(int irq, void *dev_id)
{
	if (irq == key2_res->start)
		printk("K2 pressed\n");
	else
		printk("K3 pressed\n");

	return IRQ_HANDLED;
}

static int fskey_probe(struct platform_device *pdev)
{
	int ret;

	key2_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	key3_res = platform_get_resource(pdev, IORESOURCE_IRQ, 1);

	if (!key2_res || !key3_res) {
		ret = -ENOENT;
		goto res_err;
	}

	ret = request_irq(key2_res->start, fskey_handler, key2_res->flags & IRQF_TRIGGER_MASK, "key2", NULL);
	if (ret)
		goto key2_err;
	ret = request_irq(key3_res->start, fskey_handler, key3_res->flags & IRQF_TRIGGER_MASK, "key3", NULL);
	if (ret)
		goto key3_err;

	return 0;

key3_err:
	free_irq(key2_res->start, NULL);
key2_err:
res_err:
	return ret;
}

static int fskey_remove(struct platform_device *pdev)
{
	free_irq(key3_res->start, NULL);
	free_irq(key2_res->start, NULL);

	return 0;
}

static const struct of_device_id fskey_of_matches[] = {
	{ .compatible = "fs4412,fskey", },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fskey_of_matches);

struct platform_driver fskey_drv = { 
	.driver = { 
		.name    = "fskey",
		.owner   = THIS_MODULE,
		.of_match_table = of_match_ptr(fskey_of_matches),
	},  
	.probe   = fskey_probe,
	.remove  = fskey_remove,
};

module_platform_driver(fskey_drv);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <e-mail>");
MODULE_DESCRIPTION("A simple device driver for Keys on FS4412 board");

        代码第 33 行和第 34 行使用 platform_get_resouree 分别获取了这两个中断资源,代码 第41行和第 44 行则分别注册了这两个中断,起始的中断号就是资源中的 start 成员,两个中断的处理函数都是 fskey_handler。在 skey_handler 函数中,根据中断号 irq 来确定哪9音个按键按下了。这个驱动也不是字符设备驱动,只是一个简单的按键中断测试的模块而已。相应的编译及测试命令如下。

        驱动加载后,按下按键在控制台上将会有打印。我们会发现,按键只按了一次,可能会打印几次,选成这个结果的原因就是大家所熟悉的按键抖动。因为一些机械特性,使得在按下和松开按键的时候,产生的波形并不是完美的,现实的波形大概如图所示

 

        在上面的波形中我们看到,下降沿产生了好几次,这就导致中断被触发了好几次。为了解决这个问题,我们必须要对其做消抖处理,简单的做法是使用一个定时器来计算两次中断的时间间隔,如果太小则忽略之后的中断。不过使用中断的方式还是会有一个问题,试想一下,当你长按电脑键盘的一个键会出现什么情况?一般情况下,我们应该能够报告按键的按下、抬起和长按三种事件才比较友好,为此,我们使用下一节的方法来实现一个比较实用的按键设备驱动。


三、基于输入子系统的按键驱动


        如果想让我们的按键像键盘一样很酷地工作,那么我们就不得不利用内核中的输入子系统。简单来说,输入子系统就是为所有输入设备对上层提供统一接口的一个子系统常见的输入设备有键盘、鼠标、手写输入板、游戏杆和触摸板等。输入子系统都为其定义了相应的标准,比如规定了输入事件的表示、按键值和鼠标的相对坐标等。输入子系统的大致层次结构如图所示。


        驱动用于实时获取输入硬件的输入数据,事件处理层用于处理驱动报告的数据,并对上层提供标准的事件信息,输入核心层则用来管理这两层,并建立沟通的桥梁。这个输入子系统还是比较复杂的,但是我们只是编写一个输入设备驱动的话,认识一个重要的数据结构struct input_dev 和几个常用的API就可以了。

struct input_dev {  
    /* 设备名称 */  
    const char *name;  
  
    /* 设备驱动程序标识符 */  
    const char *phys;  
  
    /* 设备总线类型 */  
    const char *id;  
    struct input_id id_table[MAX_ID];  
  
    /* 设备支持的事件类型 */  
    unsigned long evbit[NBITS(EV_MAX)];  
  
    /* 事件处理回调函数 */  
    void (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);  
  
    /* 设备索引 */  
    int devindex;  
  
    /* 设备支持的按键映射 */  
    unsigned long keybit[NBITS(KEY_MAX)];  
    /* ... */  
};


        一个输入设备用structinput dev 结构对象来表示,主要成员的意义如下

        name:输入设备的名字。
        phys: 在系统层次结构中设备的物理路径。
        id:输入设备的id,包含总线类型、制造商ID、产品ID 和版本号等evbit: 设备能够报告的事件类型,比如 EV KEY 表示设备能够报告按键事件EV REL表示设备能够报告相对坐标事件。
        keybit:设备能够报告的按键值
        keycodemax:按键编码表的大小
        keycodesize:按键编码表每个编码的大小
        keycode:按键编码表。
        输入设备驱动的几个重要API如下

struct input_dev *input_allocate_device(void);
void input_free_device(struct input_dev *dev);
int input_register_device(struct input_dev *dev);
void input_unregister_device(struct input_dev *dev);
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
void input_report_key(struct input_dev *dev, unsigned int code, int value);
void input_sync(struct input_dev *dev);


        input_allocate_device:动态分配一个 struct input_dev 结对象,返回对象的地址NULL 表示失败。
        input free_device:释放输入设备 dev。
        input register_device:注册输入设备 dev。
        input unregister_device:注销输入设备dev。
        input_event:报告一个事件,dev 是报告事件的输入设备,type 是事件类型,code是编码,value 是具体值,根据事件类型的不同而不同。
        input report_key:input_event的封装,报告的事件类型为EV_KEY
        input sync:同步事件。
一个输入设备驱动的实现步骤一般如下。
(1)使用input_allocate_device 分配一个输入设备对象。
(2)初始化输入设备对象,包括名字、路径、ID、能报告的事件类型和与编码表相关的内容等。
(3)使用input_register_device 注册输入设备。
(4)在输入设备产生事件时使用input event 报告事件
(5)使用input sync同步事件。(6)在不需要输入设备时使用input_unregister_device注销设备,并用input free_device
        介绍完输入子系统及其相关的编程步骤后,我们来讨论按键值的获取和消抖处理。释放其内存。首先,我们使用扫描的方式来获取按键值,而不是中断的方式。也就是将管脚配置为输入,然后定期读取管脚的电平,根据电平的高低来判断按键的状态。其次,关于消抖的处理可以按照下图的方式进行。

 


        假设驱动程序每隔 50ms 来扫描按键,每次扫描则要多次获取按键的电平高低值,当连续 3次读到的按键的电平高低值都一致,才认为成功获取了按键的值,再进行报告。在图9.5 中,刚开始读到的电平为高,继续读时,电平为低,所以计数清零,再次读取,电平又变高,计数又清零,如此一直继续下去,直到计数为 2为止。下一次扫描是按镜一直被按下,没有抖动,所以计数很顺利到 2。再下一次扫描,也是出现了抖动,计数值不断清零,直到稳定成高电平为止。扫描的间隔时间、每次采样的间隔时间以及计数值可以根据具体的硬件设备而定。
        我们前面说过,内核专门针对 GPIO 硬件编写了一个 GPIO 框架代码,这使得我们对GPIO的编程变得更加容易,下面就先来看看这些主要的API。

int gpio_request(unsigned gpio,const char *label);
int gpio_request_array(const struct gpio *array; size_t num);
void gpio_free(unsigned gpio);\
void gpio_free_array(const struct gpio *array,size_t num);
int gpio direction_input(unsigned gpio);
int gpio direction_output(unsigned gpio,int value);
int gpio get_value(unsigned gpio);
void gpio set_value(unsigned gpio,int value);
int of_get_gpio(struct device_node *np,int index);


        gpio_request: 申请一个 GPIO,并取名为 label,返回0表示成功.

        gpio_request_array:申请一组 GPIO,返回0表示成功。
        gpio_free:释放一个GPIO。
        gpio_free_array:释放一组GPIO。
        gpio_direction_input:设置GPIO管脚为输入
        gpio_direction_output: 设置GPIO 管脚为输出
        gpio_get_value:获取输入 GPIO管脚的状态。
        gpio_set_value:设置输出GPIO管脚的输出电平
        of_get_gpio:从设备节点np 中获取第 index个GPIO,成功返GPIO编号,失败返回一个负数。
        使用上面的API对GPIO进行编程通常包含下面几个步骤。
        (1)使用of_get_gpio 获取设备节点中描述的GPIO对应的编号。
        (2)使用 gpio_request 申请对 GPIO 管脚的使用权限如果已经被其他驱动先申请了那么再次申请会失败,这种用法和我们之前的 I/O 内存的使用方法是一样的。
        (3)使用 gpio_direction input 或 gpio direction output 将 GPIO 管脚配置为输入或输出。

        (4)使用 gpio_get_value 或 gpio set_value 来获取输入 GPIO管脚的状态或设置输出GPIO管脚的输出电平。
        (5)如果不再使用GPIO管脚,则应该使用 gpio free 来释放GPIO管脚。有了上面的各方面知识后,我们就可以来编写一个基于输入子系统的按键驱动,代码如下
 

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/slab.h>
#include <linux/delay.h>

#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/input-polldev.h>
#include <linux/platform_device.h>

#define MAX_KEYS_NUM		(8)
#define SCAN_INTERVAL		(50)	/* ms */
#define KB_ACTIVATE_DELAY	(20)	/* us */
#define KBDSCAN_STABLE_COUNT	(3)

struct fskey_dev {
	unsigned int count;
	unsigned int kstate[MAX_KEYS_NUM];
	unsigned int kcount[MAX_KEYS_NUM];
	unsigned char keycode[MAX_KEYS_NUM];
	int gpio[MAX_KEYS_NUM];
	struct input_polled_dev *polldev;
};

static void fskey_poll(struct input_polled_dev *dev)
{
	unsigned int index;
	unsigned int kstate;
	struct fskey_dev *fskey = dev->private;

	for (index = 0; index < fskey->count; index++)
		fskey->kcount[index] = 0;

	index = 0;
	do {
		udelay(KB_ACTIVATE_DELAY);
		kstate = gpio_get_value(fskey->gpio[index]);
		if (kstate != fskey->kstate[index]) {
			fskey->kstate[index] = kstate;
			fskey->kcount[index] = 0;
		} else {
			if (++fskey->kcount[index] >= KBDSCAN_STABLE_COUNT) {
				input_report_key(dev->input, fskey->keycode[index], !kstate);
				index++;
			}
		}
	} while (index < fskey->count);

	input_sync(dev->input);
}

static int fskey_probe(struct platform_device *pdev)
{
	int ret;
	int index;
	struct fskey_dev *fskey;

	fskey = kzalloc(sizeof(struct fskey_dev), GFP_KERNEL);
	if (!fskey)
		return -ENOMEM;

	platform_set_drvdata(pdev, fskey);

	for (index = 0; index < MAX_KEYS_NUM; index++) {
		ret = of_get_gpio(pdev->dev.of_node, index);
		if (ret < 0)
			break;
		else
			fskey->gpio[index] = ret;
	}

	if (!index)
		goto gpio_err;
	else
		fskey->count = index;

	for (index = 0; index < fskey->count; index++) {
		ret = gpio_request(fskey->gpio[index], "KEY");
		if (ret)
			goto req_err;

		gpio_direction_input(fskey->gpio[index]);
		fskey->keycode[index] = KEY_2 + index;
		fskey->kstate[index] = 1;
	}

	fskey->polldev = input_allocate_polled_device();
	if (!fskey->polldev) {
		ret = -ENOMEM;
		goto req_err;
	}

	fskey->polldev->private = fskey;
	fskey->polldev->poll = fskey_poll;
	fskey->polldev->poll_interval = SCAN_INTERVAL;

	fskey->polldev->input->name = "FS4412 Keyboard";
	fskey->polldev->input->phys = "fskbd/input0";
	fskey->polldev->input->id.bustype = BUS_HOST;
	fskey->polldev->input->id.vendor = 0x0001;
	fskey->polldev->input->id.product = 0x0001;
	fskey->polldev->input->id.version = 0x0100;
	fskey->polldev->input->dev.parent = &pdev->dev;

	fskey->polldev->input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
	fskey->polldev->input->keycode = fskey->keycode;
	fskey->polldev->input->keycodesize = sizeof(unsigned char);
	fskey->polldev->input->keycodemax = index;

	for (index = 0; index < fskey->count; index++)
		__set_bit(fskey->keycode[index], fskey->polldev->input->keybit);
	__clear_bit(KEY_RESERVED, fskey->polldev->input->keybit);

	ret = input_register_polled_device(fskey->polldev);
	if (ret)
		goto reg_err;

	return 0;

reg_err:
	input_free_polled_device(fskey->polldev);
req_err:
	for (index--; index >= 0; index--)
		gpio_free(fskey->gpio[index]);
gpio_err:
	kfree(fskey);
	return ret;
}

static int fskey_remove(struct platform_device *pdev)
{
	unsigned int index;
	struct fskey_dev *fskey = platform_get_drvdata(pdev);

	input_unregister_polled_device(fskey->polldev);
	input_free_polled_device(fskey->polldev);
	for (index = 0; index < fskey->count; index++)
		gpio_free(fskey->gpio[index]);
	kfree(fskey);

	return 0;
}

static const struct of_device_id fskey_of_matches[] = {
	{ .compatible = "fs4412,fskey", },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fskey_of_matches);

struct platform_driver fskey_drv = { 
	.driver = { 
		.name    = "fskey",
		.owner   = THIS_MODULE,
		.of_match_table = of_match_ptr(fskey_of_matches),
	},  
	.probe   = fskey_probe,
	.remove  = fskey_remove,
};

module_platform_driver(fskey_drv);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin Jiang <name<e-mail>");
MODULE_DESCRIPTION("A simple device driver for Keys on FS4412 board");


        代码第20行至第 27行定义了一个结构类型 struct fskey_dev,count 成员表示共有多少个按键: kstate 用于记录按键的上一次状态;kcount用来记录已经连续多少次采样得到同样的状态,用于消抖;keycode 是报告给上层的按键编码;gpio 用来记录 GPIO 编号:polldev 是一个 polldev 输入设备,特指通过轮询来得到输入设备的输入数据的设备,相当于struct input_dev 的子类,主要是多了一个 poll函数来轮询输入设备。
        代码第 62行动态分配了上面所说的结构对象。第68 行至第 79 行用于取设备树中描述的GPIO对应的编号,并得到了总的 GPIO管脚数量。第 81 行至第 89 行则分别申请了这些GPIO的使用权,并都配置为输入,还将按键编码设置为数字键2及之后顺序的值,状态为 1,表示按键是松开的。
        代码第91行动态分配了一个struct input_polled_dev 结构对象。第97行将 fskey保存在private成员中,方便之后的函数通过struct input_polled_dev 结构对象来获取fskey。第98 行指定了轮询的函数 fskey_poll,在这个函数中将会检测按键的状态并上报键值。poll_interval是轮询的间隔,这里指定为50ms。第 101行至第 107行是对输入设备结构对象的初始化,包括名字、路径和ID。第 109 行指定该输入设备能够报告 EV_KEY 时间,并且EV_REP说明驱动支持长按键的检测。keycode、keycodesize 和 keycodemax 则是与按键编码表相关的初始化。第 114 行至第 116 行设置了输入设备能够报告的按键编码,回忆前面的 keybit的描述。KEY_RESERVED是编号为0的键,使用 __clear_bit清除,则表示不报告该键。代码第 118 行注册了该输入设备。
        在 fskey_poll 函数中,使用了前面介绍的算法来进行消抖,每次采样的间隔设置为20us。当连续 3 次采样的状态都一致时则使用 imput_report_key 报告键值,然后继续对下一个按键进行扫描。最后使用 input_sync 来同步,即把之前报告的键值同步到上层。另外,报告的键值!kstate 表示按键是被按下还是被释放。
        要编译该驱动,必须要确保内核支持了轮询的输入设备,使用 make ARCH=arm menuconfig命令,在配置界面中做如下配置。


        需要特别注意的是,在 arch/arm/boot/dts/exynos4412-fs4412,dts 设备树文件中,上面的两个GPIO 已经在另外的节点中用到了,但是实际的硬件却没有用作其他节点中定义的功能,所以要保证我们的驱动能成功申请到 GPIO,必须要把那些节点修改好或删除。这里选择删除,涉及的节点有 regulators、pinctrl@11000000 和 keypad@100A0000。修改好设备树源文件后,按照前面的方法进行编译。最后将新生成的文件复制到TFTP 服务器指定的目录。

这里有一个致命bug有个io在mmc读取寄存器时用到了,这个取消配置后编译通不过。少写了一个分号吐了


测试用的应用层代码如下,因为代码很简单,这里不再进行说明。
 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>

#include <sys/stat.h>
#include <sys/types.h>

#include <linux/input.h>


#define KEY_DEV_PATH "/dev/input/event0"

int fs4412_get_key(void)
{
	int fd; 
	struct input_event event;

	fd = open(KEY_DEV_PATH, O_RDONLY);
	if(fd == -1) {
		perror("fskey->open");
		return -1;
	}

	while (1) {
		if(read(fd, &event, sizeof(event)) == sizeof(event)) {
			if (event.type == EV_KEY && event.value == 1) {
				close(fd);
				return event.code;
			} else
				continue;
		} else {
			close(fd);
			fprintf(stderr, "fskey->read: read failed\n");
			return -1;
		}
	}
}

int main(int argc, char *argv[])
{
	while (1)
		printf("key value: %d\n", fs4412_get_key());
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
书名:《Android底层开发技术实战详解——内核、移植和驱动》(电子工业出版社.王振丽)。本书从底层原理开始讲起,结合真实的案例向读者详细介绍了android内核、移植和驱动开发的整个流程。全书分为19章,依次讲解驱动移植的必要性,何为hal层深入分析,goldfish、msm、map内核和驱动解析,显示系统、输入系统、振动器系统、音频系统、视频输出系统的驱动,openmax多媒体、多媒体插件框架,传感器、照相机、wi-fi、蓝牙、gps和电话系统等。在每一章中,重点介绍了与Android驱动开发相关的底层知识,并对Android源码进行了剖析。 本书适合Android研发人员及Android爱好者学习,也可以作为相关培训学校和大专院校相关专业的教学用书。 全书压缩打包成3部分,这是第3部分。 目录: 第1章 Android底层开发基础 1 1.1 什么是驱动 1 1.1.1 驱动程序的魅力 1 1.1.2 电脑中的驱动 2 1.1.3 手机中的驱动程序 2 1.2 开源还是不开源的问题 3 1.2.1 雾里看花的开源 3 1.2.2 从为什么选择java谈为什么不开源驱动程序 3 1.2.3 对驱动开发者来说是一把双刃剑 4 1.3 Android和Linux 4 1.3.1 Linux简介 5 1.3.2 Android和Linux的关系 5 1.4 简析Linux内核 8 1.4.1 内核的体系结构 8 1.4.2 和Android密切相关的Linux内核知识 10 1.5 分析Linux内核源代码很有必要 14 1.5.1 源代码目录结构 14 1.5.2 浏览源代码的工具 16 1.5.3 为什么用汇编语言编写内核代码 17 1.5.4 Linux内核的显著特性 18 1.5.5 学习Linux内核的方法 26 第2章 分析Android源代码 31 2.1 搭建Linux开发环境和工具 31 2.1.1 搭建Linux开发环境 31 2.1.2 设置环境变量 32 2.1.3 安装编译工具 32 2.2 获取Android源代码 33 2.3 分析并编译Android源代码 35 2.3.1 Android源代码的结构 35 2.3.2 编译Android源代码 40 2.3.3 运行Android源代码 42 2.3.4 实践演练——演示编译Android程序的两种方法 43 2.4 编译Android kernel 47 2.4.1 获取goldfish内核代码 47 2.4.2 获取msm内核代码 50 2.4.3 获取omap内核代码 50 2.4.4 编译Android的Linux内核 50 2.5 运行模拟器 52 2.5.1 Linux环境下运行模拟器的方法 53 2.5.2 模拟器辅助工具——adb 54 第3章 驱动需要移植 57 3.1 驱动开发需要做的工作 57 3.2 Android移植 59 3.2.1 移植的任务 60 3.2.2 移植的内容 60 3.2.3 驱动开发的任务 61 3.3 Android对Linux的改造 61 3.3.1 Android对Linux内核文件的改动 62 3.3.2 为Android构建 Linux的操作系统 63 3.4 内核空间和用户空间接口是一个媒介 64 3.4.1 内核空间和用户空间的相互作用 64 3.4.2 系统和硬件之间的交互 64 3.4.3 使用relay实现内核到用户空间的数据传输 66 3.5 三类驱动程序 70 3.5.1 字符设备驱动程序 70 3.5.2 块设备驱动程序 79 3.5.3 网络设备驱动程序 82 第4章 hal层深入分析 84 4.1 认识hal层 84 4.1.1 hal层的发展 84 4.1.2 过去和现在的区别 86 4.2 分析hal层源代码 86 4.2.1 分析hal moudle 86 4.2.2 分析mokoid工程 89 4.3 总结hal层的使用方法 98 4.4 传感器在hal层的表现 101 4.4.1 hal层的sensor代码 102 4.4.2 总结sensor编程的流程 104 4.4.3 分析sensor源代码看Android api 与硬件平台的衔接 104 4.5 移植总结 116 4.5.1 移植各个Android部件的方式 116 4.5.2 移植技巧之一——不得不说的辅助工作 117 第5章 goldfish下的驱动解析 125 5.1 staging驱动 125 5.1.1 staging驱动概述 125 5.1.2 binder驱动程序 126 5.1.3 logger驱动程序 135 5.1.4 lowmemorykiller组件 136 5.1.5 timed output驱动程序 137 5.1.6 timed gpio驱动程序 139 5.1.7 ram console驱动程序 139 5.2 wakelock和early_suspend 140 5.2.1 wakelock和early_suspend的原理 140 5.2.2 Android休眠 141 5.2.3 Android唤醒 144 5.3 ashmem驱动程序 145 5.4 pmem驱动程序 148 5.5 alarm驱动程序 149 5.5.1 alarm简析 149 5.5.2 alarm驱动程序的实现 150 5.6 usb gadget驱动程序151 5.7 Android paranoid驱动程序153 5.8 goldfish设备驱动154 5.8.1 framebuffer驱动155 5.8.2 键盘驱动159 5.8.3 实时时钟驱动程序160 5.8.4 tty终端驱动程序161 5.8.5 nandflash驱动程序162 5.8.6 mmc驱动程序162 5.8.7 电池驱动程序162 第6章 msm内核和驱动解析164 6.1 msm基础164 6.1.1 常见msm处理器产品164 6.1.2 snapdragon内核介绍165 6.2 移植msm内核简介166 6.3 移植msm168 6.3.1 makefile文件168 6.3.2 驱动和组件170 6.3.3 设备驱动172 6.3.4 高通特有的组件174 第7章 omap内核和驱动解析177 7.1 omap基础177 7.1.1 omap简析177 7.1.2 常见omap处理器产品177 7.1.3 开发平台178 7.2 omap内核178 7.3 移植omap体系结构180 7.3.1 移植omap平台180 7.3.2 移植omap处理器183 7.4 移植Android专用驱动和组件188 7.5 omap的设备驱动190 第8章 显示系统驱动应用195 8.1 显示系统介绍195 8.1.1 Android的版本195 8.1.2 不同版本的显示系统195 8.2 移植和调试前的准备196 8.2.1 framebuffer驱动程序196 8.2.2 硬件抽象层198 8.3 实现显示系统的驱动程序210 8.3.1 goldfish中的framebuffer驱动程序210 8.3.2 使用gralloc模块的驱动程序214 8.4 msm高通处理器中的显示驱动实现224 8.4.1 msm中的framebuffer驱动程序225 8.4.2 msm中的gralloc驱动程序227 8.5 omap处理器中的显示驱动实现235 第9章 输入系统驱动应用239 9.1 输入系统介绍239 9.1.1 Android输入系统结构元素介绍239 9.1.2 移植Android输入系统时的工作240 9.2 input输入驱动241 9.3 模拟器的输入驱动256 9.4 msm高通处理器中的输入驱动实现257 9.4.1 触摸屏驱动257 9.4.2 按键和轨迹球驱动264 9.5 omap处理器平台中的输入驱动实现266 9.5.1 触摸屏驱动267 9.5.2 键盘驱动267 第10章 振动器系统驱动269 10.1 振动器系统结构269 10.1.1 硬件抽象层271 10.1.2 jni框架部分272 10.2 开始移植273 10.2.1 移植振动器驱动程序273 10.2.2 实现硬件抽象层274 10.3 在msm平台实现振动器驱动275 第11章 音频系统驱动279 11.1 音频系统结构279 11.2 分析音频系统的层次280 11.2.1 层次说明280 11.2.2 media库中的audio框架281 11.2.3 本地代码284 11.2.4 jni代码288 11.2.5 java代码289 11.3 移植audio系统的必备技术289 11.3.1 移植audio系统所要做的工作289 11.3.2 分析硬件抽象层290 11.3.3 分析audioflinger中的audio硬件抽象层的实现291 11.4 真正实现audio硬件抽象层298 11.5 msm平台实现audio驱动系统298 11.5.1 实现audio驱动程序298 11.5.2 实现硬件抽象层299 11.6 oss平台实现audio驱动系统304 11.6.1 oss驱动程序介绍304 11.6.2 mixer305 11.7 alsa平台实现audio系统312 11.7.1 注册音频设备和音频驱动312 11.7.2 在Android中使用alsa声卡313 11.7.3 在omap平台移植Android的alsa声卡驱动322 第12章 视频输出系统驱动326 12.1 视频输出系统结构326 12.2 需要移植的部分328 12.3 分析硬件抽象层328 12.3.1 overlay系统硬件抽象层的接口328 12.3.2 实现overlay系统的硬件抽象层331 12.3.3 实现接口332 12.4 实现overlay硬件抽象层333 12.5 在omap平台实现overlay系统335 12.5.1 实现输出视频驱动程序335 12.5.2 实现overlay硬件抽象层337 12.6 系统层调用overlay hal的架构342 12.6.1 调用overlay hal的架构的流程342 12.6.2 s3c6410 Android overlay的测试代码346 第13章 openmax多媒体框架349 13.1 openmax基本层次结构349 13.2 分析openmax框架构成350 13.2.1 openmax总体层次结构350 13.2.2 openmax il层的结构351 13.2.3 Android中的openmax354 13.3 实现openmax il层接口354 13.3.1 openmax il层的接口354 13.3.2 在openmax il层中需要做什么361 13.3.3 研究Android中的openmax适配层361 13.4 在omap平台实现openmax il363 13.4.1 实现文件364 13.4.2 分析ti openmax il的核心365 13.4.3 实现ti openmax il组件实例368 第14章 多媒体插件框架373 14.1 Android多媒体插件373 14.2 需要移植的内容374 14.3 opencore引擎375 14.3.1 opencore层次结构375 14.3.2 opencore代码结构376 14.3.3 opencore编译结构377 14.3.4 opencore oscl381 14.3.5 实现opencore中的openmax部分383 14.3.6 opencore的扩展398 14.4 stagefright引擎404 14.4.1 stagefright代码结构404 14.4.2 stagefright实现openmax接口405 14.4.3 video buffer传输流程409 第15章 传感器系统415 15.1 传感器系统的结构415 15.2 需要移植的内容417 15.2.1 移植驱动程序417 15.2.2 移植硬件抽象层418 15.2.3 实现上层部分419 15.3 在模拟器中实现传感器424 第16章 照相机系统430 16.1 camera系统的结构430 16.2 需要移植的内容433 16.3 移植和调试433 16.3.1 v4l2驱动程序433 16.3.2 硬件抽象层441 16.4 实现camera系统的硬件抽象层446 16.4.1 java程序部分446 16.4.2 camera的java本地调用部分447 16.4.3 camera的本地库libui.so448 16.4.4 camera服务libcameraservice.so449 16.5 msm平台实现camera系统454 16.6 omap平台实现camera系统457 第17章 wi-fi系统、蓝牙系统和gps系统459 17.1 wi-fi系统459 17.1.1 wi-fi系统的结构459 17.1.2 需要移植的内容461 17.1.3 移植和调试461 17.1.4 omap平台实现wi-fi469 17.1.5 配置wi-fi的流程471 17.1.6 具体演练——在Android下实现ethernet473 17.2 蓝牙系统475 17.2.1 蓝牙系统的结构475 17.2.2 需要移植的内容477 17.2.3 具体移植478 17.2.4 msm平台的蓝牙驱动480 17.3 定位系统482 17.3.1 定位系统的结构483 17.3.2 需要移植的内容484 17.3.3 移植和调试484 第18章 电话系统498 18.1 电话系统基础498 18.1.1 电话系统简介498 18.1.2 电话系统结构500 18.2 需要移植的内容501 18.3 移植和调试502 18.3.1 驱动程序502 18.3.2 ril接口504 18.4 电话系统实现流程分析507 18.4.1 初始启动流程507 18.4.2 request流程509 18.4.3 response流程512 第19章 其他系统514 19.1 alarm警报器系统514 19.1.1 alarm系统的结构514 19.1.2 需要移植的内容515 19.1.3 移植和调试516 19.1.4 模拟器环境的具体实现518 19.1.5 msm平台实现alarm518 19.2 lights光系统519 19.2.1 lights光系统的结构520 19.2.2 需要移植的内容521 19.2.3 移植和调试521 19.2.4 msm平台实现光系统523 19.3 battery电池系统524 19.3.1 battery系统的结构524 19.3.2 需要移植的内容526 19.3.3 移植和调试526 19.3.4 在模拟器中实现电池系统529
书名:《Android底层开发技术实战详解——内核、移植和驱动》(电子工业出版社.王振丽)。本书从底层原理开始讲起,结合真实的案例向读者详细介绍了android内核、移植和驱动开发的整个流程。全书分为19章,依次讲解驱动移植的必要性,何为hal层深入分析,goldfish、msm、map内核和驱动解析,显示系统、输入系统、振动器系统、音频系统、视频输出系统的驱动,openmax多媒体、多媒体插件框架,传感器、照相机、wi-fi、蓝牙、gps和电话系统等。在每一章中,重点介绍了与Android驱动开发相关的底层知识,并对Android源码进行了剖析。 本书适合Android研发人员及Android爱好者学习,也可以作为相关培训学校和大专院校相关专业的教学用书。 全书压缩打包成3部分,这是第1部分。 目录: 第1章 Android底层开发基础 1 1.1 什么是驱动 1 1.1.1 驱动程序的魅力 1 1.1.2 电脑中的驱动 2 1.1.3 手机中的驱动程序 2 1.2 开源还是不开源的问题 3 1.2.1 雾里看花的开源 3 1.2.2 从为什么选择java谈为什么不开源驱动程序 3 1.2.3 对驱动开发者来说是一把双刃剑 4 1.3 Android和Linux 4 1.3.1 Linux简介 5 1.3.2 Android和Linux的关系 5 1.4 简析Linux内核 8 1.4.1 内核的体系结构 8 1.4.2 和Android密切相关的Linux内核知识 10 1.5 分析Linux内核源代码很有必要 14 1.5.1 源代码目录结构 14 1.5.2 浏览源代码的工具 16 1.5.3 为什么用汇编语言编写内核代码 17 1.5.4 Linux内核的显著特性 18 1.5.5 学习Linux内核的方法 26 第2章 分析Android源代码 31 2.1 搭建Linux开发环境和工具 31 2.1.1 搭建Linux开发环境 31 2.1.2 设置环境变量 32 2.1.3 安装编译工具 32 2.2 获取Android源代码 33 2.3 分析并编译Android源代码 35 2.3.1 Android源代码的结构 35 2.3.2 编译Android源代码 40 2.3.3 运行Android源代码 42 2.3.4 实践演练——演示编译Android程序的两种方法 43 2.4 编译Android kernel 47 2.4.1 获取goldfish内核代码 47 2.4.2 获取msm内核代码 50 2.4.3 获取omap内核代码 50 2.4.4 编译Android的Linux内核 50 2.5 运行模拟器 52 2.5.1 Linux环境下运行模拟器的方法 53 2.5.2 模拟器辅助工具——adb 54 第3章 驱动需要移植 57 3.1 驱动开发需要做的工作 57 3.2 Android移植 59 3.2.1 移植的任务 60 3.2.2 移植的内容 60 3.2.3 驱动开发的任务 61 3.3 Android对Linux的改造 61 3.3.1 Android对Linux内核文件的改动 62 3.3.2 为Android构建 Linux的操作系统 63 3.4 内核空间和用户空间接口是一个媒介 64 3.4.1 内核空间和用户空间的相互作用 64 3.4.2 系统和硬件之间的交互 64 3.4.3 使用relay实现内核到用户空间的数据传输 66 3.5 三类驱动程序 70 3.5.1 字符设备驱动程序 70 3.5.2 块设备驱动程序 79 3.5.3 网络设备驱动程序 82 第4章 hal层深入分析 84 4.1 认识hal层 84 4.1.1 hal层的发展 84 4.1.2 过去和现在的区别 86 4.2 分析hal层源代码 86 4.2.1 分析hal moudle 86 4.2.2 分析mokoid工程 89 4.3 总结hal层的使用方法 98 4.4 传感器在hal层的表现 101 4.4.1 hal层的sensor代码 102 4.4.2 总结sensor编程的流程 104 4.4.3 分析sensor源代码看Android api 与硬件平台的衔接 104 4.5 移植总结 116 4.5.1 移植各个Android部件的方式 116 4.5.2 移植技巧之一——不得不说的辅助工作 117 第5章 goldfish下的驱动解析 125 5.1 staging驱动 125 5.1.1 staging驱动概述 125 5.1.2 binder驱动程序 126 5.1.3 logger驱动程序 135 5.1.4 lowmemorykiller组件 136 5.1.5 timed output驱动程序 137 5.1.6 timed gpio驱动程序 139 5.1.7 ram console驱动程序 139 5.2 wakelock和early_suspend 140 5.2.1 wakelock和early_suspend的原理 140 5.2.2 Android休眠 141 5.2.3 Android唤醒 144 5.3 ashmem驱动程序 145 5.4 pmem驱动程序 148 5.5 alarm驱动程序 149 5.5.1 alarm简析 149 5.5.2 alarm驱动程序的实现 150 5.6 usb gadget驱动程序151 5.7 Android paranoid驱动程序153 5.8 goldfish设备驱动154 5.8.1 framebuffer驱动155 5.8.2 键盘驱动159 5.8.3 实时时钟驱动程序160 5.8.4 tty终端驱动程序161 5.8.5 nandflash驱动程序162 5.8.6 mmc驱动程序162 5.8.7 电池驱动程序162 第6章 msm内核和驱动解析164 6.1 msm基础164 6.1.1 常见msm处理器产品164 6.1.2 snapdragon内核介绍165 6.2 移植msm内核简介166 6.3 移植msm168 6.3.1 makefile文件168 6.3.2 驱动和组件170 6.3.3 设备驱动172 6.3.4 高通特有的组件174 第7章 omap内核和驱动解析177 7.1 omap基础177 7.1.1 omap简析177 7.1.2 常见omap处理器产品177 7.1.3 开发平台178 7.2 omap内核178 7.3 移植omap体系结构180 7.3.1 移植omap平台180 7.3.2 移植omap处理器183 7.4 移植Android专用驱动和组件188 7.5 omap的设备驱动190 第8章 显示系统驱动应用195 8.1 显示系统介绍195 8.1.1 Android的版本195 8.1.2 不同版本的显示系统195 8.2 移植和调试前的准备196 8.2.1 framebuffer驱动程序196 8.2.2 硬件抽象层198 8.3 实现显示系统的驱动程序210 8.3.1 goldfish中的framebuffer驱动程序210 8.3.2 使用gralloc模块的驱动程序214 8.4 msm高通处理器中的显示驱动实现224 8.4.1 msm中的framebuffer驱动程序225 8.4.2 msm中的gralloc驱动程序227 8.5 omap处理器中的显示驱动实现235 第9章 输入系统驱动应用239 9.1 输入系统介绍239 9.1.1 Android输入系统结构元素介绍239 9.1.2 移植Android输入系统时的工作240 9.2 input输入驱动241 9.3 模拟器的输入驱动256 9.4 msm高通处理器中的输入驱动实现257 9.4.1 触摸屏驱动257 9.4.2 按键和轨迹球驱动264 9.5 omap处理器平台中的输入驱动实现266 9.5.1 触摸屏驱动267 9.5.2 键盘驱动267 第10章 振动器系统驱动269 10.1 振动器系统结构269 10.1.1 硬件抽象层271 10.1.2 jni框架部分272 10.2 开始移植273 10.2.1 移植振动器驱动程序273 10.2.2 实现硬件抽象层274 10.3 在msm平台实现振动器驱动275 第11章 音频系统驱动279 11.1 音频系统结构279 11.2 分析音频系统的层次280 11.2.1 层次说明280 11.2.2 media库中的audio框架281 11.2.3 本地代码284 11.2.4 jni代码288 11.2.5 java代码289 11.3 移植audio系统的必备技术289 11.3.1 移植audio系统所要做的工作289 11.3.2 分析硬件抽象层290 11.3.3 分析audioflinger中的audio硬件抽象层的实现291 11.4 真正实现audio硬件抽象层298 11.5 msm平台实现audio驱动系统298 11.5.1 实现audio驱动程序298 11.5.2 实现硬件抽象层299 11.6 oss平台实现audio驱动系统304 11.6.1 oss驱动程序介绍304 11.6.2 mixer305 11.7 alsa平台实现audio系统312 11.7.1 注册音频设备和音频驱动312 11.7.2 在Android中使用alsa声卡313 11.7.3 在omap平台移植Android的alsa声卡驱动322 第12章 视频输出系统驱动326 12.1 视频输出系统结构326 12.2 需要移植的部分328 12.3 分析硬件抽象层328 12.3.1 overlay系统硬件抽象层的接口328 12.3.2 实现overlay系统的硬件抽象层331 12.3.3 实现接口332 12.4 实现overlay硬件抽象层333 12.5 在omap平台实现overlay系统335 12.5.1 实现输出视频驱动程序335 12.5.2 实现overlay硬件抽象层337 12.6 系统层调用overlay hal的架构342 12.6.1 调用overlay hal的架构的流程342 12.6.2 s3c6410 Android overlay的测试代码346 第13章 openmax多媒体框架349 13.1 openmax基本层次结构349 13.2 分析openmax框架构成350 13.2.1 openmax总体层次结构350 13.2.2 openmax il层的结构351 13.2.3 Android中的openmax354 13.3 实现openmax il层接口354 13.3.1 openmax il层的接口354 13.3.2 在openmax il层中需要做什么361 13.3.3 研究Android中的openmax适配层361 13.4 在omap平台实现openmax il363 13.4.1 实现文件364 13.4.2 分析ti openmax il的核心365 13.4.3 实现ti openmax il组件实例368 第14章 多媒体插件框架373 14.1 Android多媒体插件373 14.2 需要移植的内容374 14.3 opencore引擎375 14.3.1 opencore层次结构375 14.3.2 opencore代码结构376 14.3.3 opencore编译结构377 14.3.4 opencore oscl381 14.3.5 实现opencore中的openmax部分383 14.3.6 opencore的扩展398 14.4 stagefright引擎404 14.4.1 stagefright代码结构404 14.4.2 stagefright实现openmax接口405 14.4.3 video buffer传输流程409 第15章 传感器系统415 15.1 传感器系统的结构415 15.2 需要移植的内容417 15.2.1 移植驱动程序417 15.2.2 移植硬件抽象层418 15.2.3 实现上层部分419 15.3 在模拟器中实现传感器424 第16章 照相机系统430 16.1 camera系统的结构430 16.2 需要移植的内容433 16.3 移植和调试433 16.3.1 v4l2驱动程序433 16.3.2 硬件抽象层441 16.4 实现camera系统的硬件抽象层446 16.4.1 java程序部分446 16.4.2 camera的java本地调用部分447 16.4.3 camera的本地库libui.so448 16.4.4 camera服务libcameraservice.so449 16.5 msm平台实现camera系统454 16.6 omap平台实现camera系统457 第17章 wi-fi系统、蓝牙系统和gps系统459 17.1 wi-fi系统459 17.1.1 wi-fi系统的结构459 17.1.2 需要移植的内容461 17.1.3 移植和调试461 17.1.4 omap平台实现wi-fi469 17.1.5 配置wi-fi的流程471 17.1.6 具体演练——在Android下实现ethernet473 17.2 蓝牙系统475 17.2.1 蓝牙系统的结构475 17.2.2 需要移植的内容477 17.2.3 具体移植478 17.2.4 msm平台的蓝牙驱动480 17.3 定位系统482 17.3.1 定位系统的结构483 17.3.2 需要移植的内容484 17.3.3 移植和调试484 第18章 电话系统498 18.1 电话系统基础498 18.1.1 电话系统简介498 18.1.2 电话系统结构500 18.2 需要移植的内容501 18.3 移植和调试502 18.3.1 驱动程序502 18.3.2 ril接口504 18.4 电话系统实现流程分析507 18.4.1 初始启动流程507 18.4.2 request流程509 18.4.3 response流程512 第19章 其他系统514 19.1 alarm警报器系统514 19.1.1 alarm系统的结构514 19.1.2 需要移植的内容515 19.1.3 移植和调试516 19.1.4 模拟器环境的具体实现518 19.1.5 msm平台实现alarm518 19.2 lights光系统519 19.2.1 lights光系统的结构520 19.2.2 需要移植的内容521 19.2.3 移植和调试521 19.2.4 msm平台实现光系统523 19.3 battery电池系统524 19.3.1 battery系统的结构524 19.3.2 需要移植的内容526 19.3.3 移植和调试526 19.3.4 在模拟器中实现电池系统529

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宇努力学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值