1. 程式人生 > >linux裝置樹筆記__基於msm8x10的基本分析

linux裝置樹筆記__基於msm8x10的基本分析

由文章,linux裝置樹筆記__dts基本概念及語法,我們知道了基本概念,知道了大概的裝置樹節點及其屬性,而節點下的屬性大多是自定義,除了保留的幾個屬性,大多從.dts是無法知道其用途的,這個就需要看驅動是如何解析屬性值的了,這點也可作技術細節的部分隱藏。

在原始碼的msm8x12\kernel\arch\arm\boot\dts下有很多xxx.dts,xxx.dtsi,一般一個board就有一個.dts,所以你能看見很多dts檔案,即使一個SOC也可能有不同的dts,這樣不同的dts會共用很多的裝置,那麼.dtsi就是起共用裝置樹的作用,一個板子dts可以像C語言一樣來包含dtsi,如/include/ "msm8612-qrd-camera-sensor.dtsi"。

msm8x12\kernel\arch\arm\mach-msm\Makefile.boot關於編譯msm8x12的dts部分。

# MSM8610
   zreladdr-$(CONFIG_ARCH_MSM8610)	:= 0x00008000
        dtb-$(CONFIG_ARCH_MSM8610)	+= msm8610-v1-cdp.dtb
        dtb-$(CONFIG_ARCH_MSM8610)	+= msm8610-v2-cdp.dtb
        dtb-$(CONFIG_ARCH_MSM8610)	+= msm8610-v1-mtp.dtb
        dtb-$(CONFIG_ARCH_MSM8610)	+= msm8610-v2-mtp.dtb
        dtb-$(CONFIG_ARCH_MSM8610)	+= msm8610-rumi.dtb
        dtb-$(CONFIG_ARCH_MSM8610)	+= msm8610-sim.dtb
        dtb-$(CONFIG_ARCH_MSM8610)	+= msm8610-v1-qrd-skuaa.dtb
        dtb-$(CONFIG_ARCH_MSM8610)	+= msm8610-v1-qrd-skuab.dtb
        dtb-$(CONFIG_ARCH_MSM8610)	+= msm8610-v2-qrd-skuaa.dtb
        dtb-$(CONFIG_ARCH_MSM8610)	+= msm8610-v2-qrd-skuab.dtb
ifeq ($(CONFIG_ARCH_MSM8610_CB01),y)
	dtb-$(CONFIG_ARCH_MSM8610) += board-seuic-cb01.dtb
endif
ifeq ($(CONFIG_ARCH_MSM8610_D330S),y)
	dtb-$(CONFIG_ARCH_MSM8610) += board-seuic-d330s.dtb
endif
ifeq ($(CONFIG_ARCH_MSM8610_D500),y)
	dtb-$(CONFIG_ARCH_MSM8610) += board-seuic-d500.dtb
endif
ifeq ($(CONFIG_ARCH_MSM8610_D510),y)
	dtb-$(CONFIG_ARCH_MSM8610) += board-seuic-d510.dtb
endif

在msm8x12的SOC我們有3個板子型號,假設我們選擇的是d500的型號,那麼我們一般的不同硬體配置相關都是在board-seuci-d500.dtb中修改,其他的dtb目標檔案都是共有的硬體配置,因為我們都是一樣的SOC嘛。
/ {
	model = "Qualcomm MSM 8610v2 QRD SKUAB D510";
	compatible = "qcom,msm8610-qrd", "qcom,msm8610", "qcom,qrd";
	qcom,board-id = <0x6000b 3>;
};

其中model和compatible每個板子都有,前者是SOC描述,後者是board型號,後者一般用公司名加上soc型號組合,多個值表示相容board,並且不要和其他board重名,qcom,board-id是自定義的,有的板子還有qcom,msm-id。

板子中的裝置節點一般通過compatible來標示查詢的,下面提取board-seuic-d500.dtb的部分裝置進行解析並檢視驅動是如何利用的:

LED裝置

&soc {
...
	gpio-leds {
		compatible = "gpio-leds";//驅動要匹配的關鍵字
		status = "ok";

		 /*  scan_led  */
		 red1_led {
			gpios = <&pm8110_gpios 1 0x0>;
			label = "scan";
			linux,default-trigger = "heartbeat";
			default-state = "off";
		 };
		 /*  charge_ing  */
		red2_led {
			gpios = <&pm8110_gpios 3 0x0>;
			label = "red1";
			linux,default-trigger = "battery-charging";
			default-state = "off";
			retain-state-suspended = "yes";
		 };
		/*  charge_complete  */
		green_led {
			gpios = <&pm8110_gpios 2 0x0>;
			label = "green1";
			linux,default-trigger = "battery-full";
			default-state = "off";
			retain-state-suspended = "yes";
		};
	};
...
};
裝置樹中的SOC下一級的裝置gpio-leds,有三個子節點,分別是掃描燈,充電中指示燈,充電完成指示燈,下面以充電完成指示燈來說明。

對應的驅動在msm8x12\kernel\drivers\leds\leds-gpio.c,驅動中使用of相關驅動來解析dts,且全域性巨集為CONFIG_OF_GPIO。

驅動中在probe前要匹配的關鍵字是compatible的值,如下,並且匹配優先順序of_match_table > id_table > driver.name,且不管是id_table還是of_match_table都要以一個空元素結尾。

static const struct of_device_id of_gpio_leds_match[] = {
	{ .compatible = "gpio-leds", },
	{},
};
static struct platform_driver gpio_led_driver = {
	.probe		= gpio_led_probe,
	.remove		= __devexit_p(gpio_led_remove),
	.driver		= {
		.name	= "leds-gpio",
		.owner	= THIS_MODULE,
		.of_match_table = of_gpio_leds_match,
	},
};

module_platform_driver(gpio_led_driver);
匹配成功後才會呼叫probe,其中module_platform_driver(gpio_led_driver)是一個模組初始化和退出的巨集:
#define module_platform_driver(__platform_driver) \
static int __init __platform_driver##_init(void) \
{ \
	return platform_driver_register(&(__platform_driver)); \
} \
module_init(__platform_driver##_init); \
static void __exit __platform_driver##_exit(void) \
{ \
	platform_driver_unregister(&(__platform_driver)); \
} \
module_exit(__platform_driver##_exit);

gpio_led_probe->gpio_leds_create_of()函式就是解析上述LED裝置節點的過程:

static struct gpio_leds_priv * __devinit gpio_leds_create_of(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node, *child;
	struct gpio_leds_priv *priv;
	int count = 0, ret;

	/* count LEDs in this device, so we know how much to allocate */
	for_each_child_of_node(np, child)
		count++;
	if (!count)
		return NULL;

	priv = kzalloc(sizeof_gpio_leds_priv(count), GFP_KERNEL);
	if (!priv)
		return NULL;

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

		led.gpio = of_get_gpio_flags(child, 0, &flags);//獲取gpio序號及有效標誌,實際上是通過gpios屬性獲取
		led.active_low = flags & OF_GPIO_ACTIVE_LOW;//如為OF_GPIO_ACTIVE_LOW即值為1,那麼點亮set 0就是set 1,要進行邏輯取反
		led.name = of_get_property(child, "label", NULL) ? : child->name;//label屬性為子節點裝置名
		led.default_trigger =//預設的LED trigger
			of_get_property(child, "linux,default-trigger", NULL);
		state = of_get_property(child, "default-state", NULL);//開機預設的LED狀態
		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;
		}
		retain_state = of_get_property(child, "retain-state-suspended", NULL);//休眠時LED的狀態
		if (retain_state) {
			if (!strcmp(retain_state, "yes"))
				led.retain_state_suspended = 1;
			else
				led.retain_state_suspended = 0;
		}

		ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],//建立LED裝置
				      &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]);
	kfree(priv);
	return NULL;
}
如驅動中所示,大多通過of_xxx的API函式來解析dts裝置節點屬性,屬性就是KEY-VALUE結構,至於值的意義完全由驅動來控制。比如of_get_property(child, "linux,default-trigger", NULL),其中child是當前裝置節點,引數二就是待查詢的屬性,返回值是對應屬性的值,此處就是對應於DTS的linux,default-trigger = "battery-full",進行解析,返回值應該為“battery-full”,至於這個LED trigger的意義及用法見我的另一篇文章《linux驅動____LED子系統筆記》。

NOTE:另外需要注意的是,對於屬性名,是否可以隨意定義,要看of的API程式碼,比如gpios屬性名是API裡使用的,用來指示使用的gpio號及有效標誌位,就必須這麼用不能改名;

/**
 * of_get_gpio_flags() - Get a GPIO number and flags to use with GPIO API
 * @np:		device node to get GPIO from
 * @index:	index of the GPIO
 * @flags:	a flags pointer to fill in
 *
 * Returns GPIO number to use with Linux generic GPIO API, or one of the errno
 * value on the error condition. If @flags is not NULL the function also fills
 * in flags for the GPIO.
 */
static inline int of_get_gpio_flags(struct device_node *np, int index,
		      enum of_gpio_flags *flags)
{
	return of_get_named_gpio_flags(np, "gpios", index, flags);
}

而對於像"linux,default-trigger"是可以隨便定義的,因為它唯一使用的地方就是使用of相關API在驅動中獲取屬性值,完全由驅動開發者的思維邏輯控制的。

觸屏

下面再舉1個觸屏的例子,同一個I2C匯流排掛了多個裝置,DTS程式碼只貼出2個:

&soc {
...
    [email protected]{
	/*synaptics v2.3 DS4/DS5*/
	[email protected] {//節點一般以裝置名@addr組合
		compatible = "synaptics,dsx";//觸屏裝置名
		reg = <0x20>;//從裝置I2C地址
		interrupt-parent = <&msmgpio>;//I2C的中斷給誰處理
		interrupts = <1 0x2002>;//I2C中斷給msmgpio處理,後面詳細解釋
		vdd-supply = <&pm8110_l19>;//I2C裝置的VDD由電源管理晶片pm8110的pin_114供電
		vcc_i2c-supply = <&pm8110_l14>;//與上類似
		synaptics,pwr-reg-name = "vdd";//供電名
		synaptics,bus-reg-name = "vcc_i2c";//與上類似
		synaptics,irq-gpio = <&msmgpio 1 0x2008>;//具體中斷資源連那個gpio
		synaptics,irq-flags = <0x2002>;/* IRQF_ONESHOT | IRQF_TRIGGER_FALLING */
		synaptics,power-delay-ms = <160>;
		synaptics,reset-delay-ms = <100>;
		synaptics,swap-axes;
			//synaptics,x-flip;
			synaptics,y-flip;
		synaptics,irq-on-state = <0>;
	};
	
	[email protected] {
	compatible = "qcom,nfc-akm09911";//電子羅盤裝置名
	reg = <0x0d>;//I2C從地址
	vdd-supply = <&pm8110_l19>;
	vio-supply = <&pm8110_l14>;
	akm09911,layout = <3>; //廠商定製的晶片貼片時的佈局標誌
	akm09911,reset-pin = <35>;
	};
    }
...
}

驅動解析在msm8x12\kernel\drivers\input\touchscreen\synaptics_dsx\synaptics_dsx_i2c.c

dts裝置匹配驅動,我們的DTS中compatible = "synaptics,dsx"與of_match_table是中的值是一致的,匹配成功!

#ifdef CONFIG_OF
static struct of_device_id synaptics_rmi4_of_match_table[] = {
	{
		.compatible = "synaptics,dsx",
	},
	{},
};
MODULE_DEVICE_TABLE(of, synaptics_rmi4_of_match_table);
#else
#define synaptics_rmi4_of_match_table NULL
#endif
static const struct i2c_device_id synaptics_rmi4_id_table[] = {
	{"synaptics_dsx_i2c", 0},
	{},
};
MODULE_DEVICE_TABLE(i2c, synaptics_rmi4_id_table);
static struct i2c_driver synaptics_rmi4_i2c_driver = {
	.driver = {
		.name = I2C_DRIVER_NAME,
		.owner = THIS_MODULE,
		.of_match_table = synaptics_rmi4_of_match_table,
	},
	.probe = synaptics_rmi4_i2c_probe,
	.remove = __devexit_p(synaptics_rmi4_i2c_remove),
	.id_table = synaptics_rmi4_id_table,
};

在synaptics_rmi4_i2c_probe()->parse_dt()中,

static int parse_dt(struct device *dev, struct synaptics_dsx_board_data *bdata)
{
	int retval;
	u32 value;
	const char *name;
	struct property *prop;
	struct device_node *np = dev->of_node;

	bdata->irq_gpio = of_get_named_gpio_flags(np,//中斷gpio號,忽略flag
			"synaptics,irq-gpio", 0, NULL);

	retval = of_property_read_u32(np, "synaptics,irq-on-state",//中斷處理時gpio電平是H還是L
			&value);
	if (retval < 0)
		bdata->irq_on_state = 0;
	else
		bdata->irq_on_state = value;

	retval = of_property_read_u32(np, "synaptics,irq-flags", &value);//中斷觸發處理標誌
	if (retval < 0)
		return retval;
	else
		bdata->irq_flags = value;

	retval = of_property_read_string(np, "synaptics,pwr-reg-name", &name);//供電名,作為後面regulator_get的引數id
	if (retval == -EINVAL)
		bdata->pwr_reg_name = NULL;
	else if (retval < 0)
		return retval;
	else
		bdata->pwr_reg_name = name;

	retval = of_property_read_string(np, "synaptics,bus-reg-name", &name);//同上
	if (retval == -EINVAL)
		bdata->bus_reg_name = NULL;
	else if (retval < 0)
		return retval;
	else
		bdata->bus_reg_name = name;

	if (of_property_read_bool(np, "synaptics,power-gpio")) {//DTS未使用到。上電時,power_gpio初始狀態
		bdata->power_gpio = of_get_named_gpio_flags(np,
				"synaptics,power-gpio", 0, NULL);
		retval = of_property_read_u32(np, "synaptics,power-on-state",
				&value);
		if (retval < 0)
			return retval;
		else
			bdata->power_on_state = value;
	} else {
		bdata->power_gpio = -1;
	}

	if (of_property_read_bool(np, "synaptics,power-delay-ms")) {//電源上電或resume時要等待的時間
		retval = of_property_read_u32(np, "synaptics,power-delay-ms",
				&value);
		if (retval < 0)
			return retval;
		else
			bdata->power_delay_ms = value;
	} else {
		bdata->power_delay_ms = 0;
	}

	if (of_property_read_bool(np, "synaptics,reset-gpio")) {//DTS未使用
		bdata->reset_gpio = of_get_named_gpio_flags(np,
				"synaptics,reset-gpio", 0, NULL);
		retval = of_property_read_u32(np, "synaptics,reset-on-state",
				&value);
		if (retval < 0)
			return retval;
		else
			bdata->reset_on_state = value;
		retval = of_property_read_u32(np, "synaptics,reset-active-ms",
				&value);
		if (retval < 0)
			return retval;
		else
			bdata->reset_active_ms = value;
	} else {
		bdata->reset_gpio = -1;
	}

	if (of_property_read_bool(np, "synaptics,reset-delay-ms")) {//執行soft-reset時的等待時間
		retval = of_property_read_u32(np, "synaptics,reset-delay-ms",
				&value);
		if (retval < 0)
			return retval;
		else
			bdata->reset_delay_ms = value;
	} else {
		bdata->reset_delay_ms = 0;
	}

	if (of_property_read_bool(np, "synaptics,max-y-for-2d")) {//DTS未使用,觸屏報點的Y方向最大值
		retval = of_property_read_u32(np, "synaptics,max-y-for-2d",
				&value);
		if (retval < 0)
			return retval;
		else
			bdata->max_y_for_2d = value;
	} else {
		bdata->max_y_for_2d = -1;
	}

	bdata->swap_axes = of_property_read_bool(np, "synaptics,swap-axes");//觸屏報點X,Y交換

	bdata->x_flip = of_property_read_bool(np, "synaptics,x-flip");//X值變為關於X中軸對稱

	bdata->y_flip = of_property_read_bool(np, "synaptics,y-flip");//Y值變為關於Y中軸對稱

	prop = of_find_property(np, "synaptics,cap-button-codes", NULL);//DTS未使用
	if (prop && prop->length) {
		bdata->cap_button_map->map = devm_kzalloc(dev,
				prop->length,
				GFP_KERNEL);
		if (!bdata->cap_button_map->map)
			return -ENOMEM;
		bdata->cap_button_map->nbuttons = prop->length / sizeof(u32);
		retval = of_property_read_u32_array(np,
				"synaptics,cap-button-codes",
				bdata->cap_button_map->map,
				bdata->cap_button_map->nbuttons);
		if (retval < 0) {
			bdata->cap_button_map->nbuttons = 0;
			bdata->cap_button_map->map = NULL;
		}
	} else {
		bdata->cap_button_map->nbuttons = 0;
		bdata->cap_button_map->map = NULL;
	}

	prop = of_find_property(np, "synaptics,vir-button-codes", NULL);//DTS未使用
	if (prop && prop->length) {
		bdata->vir_button_map->map = devm_kzalloc(dev,
				prop->length,
				GFP_KERNEL);
		if (!bdata->vir_button_map->map)
			return -ENOMEM;
		bdata->vir_button_map->nbuttons = prop->length / sizeof(u32);
		bdata->vir_button_map->nbuttons /= 5;
		retval = of_property_read_u32_array(np,
				"synaptics,vir-button-codes",
				bdata->vir_button_map->map,
				bdata->vir_button_map->nbuttons * 5);
		if (retval < 0) {
			bdata->vir_button_map->nbuttons = 0;
			bdata->vir_button_map->map = NULL;			
		}
	} else {
		bdata->vir_button_map->nbuttons = 0;
		bdata->vir_button_map->map = NULL;
	}

	return 0;
}

of_get_named_gpio_flags(np, "synaptics,irq-gpio", 0, NULL)從DTS中獲取中斷gpio即觸屏的中斷連線到soc哪個GPIO上,然後呼叫request_threaded_irq申請中斷及處理函式。

of_property_read_u32(np, "synaptics,irq-on-state", &value),中斷中使用:

static irqreturn_t synaptics_rmi4_irq(int irq, void *data)
{
	struct synaptics_rmi4_data *rmi4_data = data;
	const struct synaptics_dsx_board_data *bdata =
			rmi4_data->hw_if->board_data;

	if (gpio_get_value(bdata->irq_gpio) != bdata->irq_on_state)//當前中斷線gpio值與irq_on_state值一致才會報點
		goto exit;

	synaptics_rmi4_sensor_report(rmi4_data);

exit:
	return IRQ_HANDLED;
}
of_property_read_u32(np, "synaptics,irq-flags", &value)是request_threaded_irq()申請中斷處理資源的中斷方式標誌,對應於DTS中的synaptics,irq-flags = <0x2002>,那麼這個值是什麼意思,就是使用標準的中斷方式定義,在msm8x12\kernel\include\linux\interrupt.h中有定義:
//hardware interrupt line behaviour flags
#define IRQF_TRIGGER_NONE	0x00000000
#define IRQF_TRIGGER_RISING	0x00000001
#define IRQF_TRIGGER_FALLING	0x00000002
#define IRQF_TRIGGER_HIGH	0x00000004
#define IRQF_TRIGGER_LOW	0x00000008
#define IRQF_TRIGGER_MASK	(IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
				 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE	0x00000010
//used only by the kernel as part of the irq handling routines.
#define IRQF_DISABLED		0x00000020
#define IRQF_SAMPLE_RANDOM	0x00000040
#define IRQF_SHARED		0x00000080
#define IRQF_PROBE_SHARED	0x00000100
#define __IRQF_TIMER		0x00000200
#define IRQF_PERCPU		0x00000400
#define IRQF_NOBALANCING	0x00000800
#define IRQF_IRQPOLL		0x00001000
#define IRQF_ONESHOT		0x00002000
#define IRQF_NO_SUSPEND		0x00004000
#define IRQF_FORCE_RESUME	0x00008000
#define IRQF_NO_THREAD		0x00010000
#define IRQF_EARLY_RESUME	0x00020000

#define IRQF_TIMER		(__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)
中斷標誌定義都是掩碼,基本常用的就是上下降沿、高低電平,還有常用的IRQF_DISABLED進行或組合。上述DTS的中斷標誌0x2002就是IRQF_ONESHOT | IRQF_TRIGGER_FALLING,即下降沿觸發中斷,且在中斷thread未完成前不開放中斷。

of_property_read_string(np, "synaptics,pwr-reg-name", &name)對應於DTS的synaptics,pwr-reg-name = "vdd",實際上是regulator的id,根據這個"vdd"的id,可查詢到系統中的vdd-supply,然後控制供電行為的enable和disable。那麼此處是如何做的呢?!

synaptics_rmi4_probe()->

synaptics_rmi4_get_reg()->

regulator_get(rmi4_data->pdev->dev.parent,bdata->pwr_reg_name)->

_regulator_get->

regulator_dev_lookup->

of_get_regulator->

regnode = of_parse_phandle(dev->of_node, prop_name, 0)

最後一個函式使用snprintf(prop_name, 32, "%s-supply", supply),也就是說返回的regnode是對應於id為“vdd-supply”的裝置節點;說到底對於struct regulator *regulator_get(struct device *dev, const char *id),他對於dts的處理就是查詢id為"$(id)-supply"對應的dts屬性值指向的regulator並返回(此處對應於DTS的vdd-supply = <&pm8110_l19>,就是查詢pm8110_119所引用的regulator並返回),這樣驅動就能去控制供電行為了。這樣在觸屏suspend()和resume()是去對應呼叫regulator_disable()和regulator_enable()進行供電的關和開。

其他DTS屬性大多是這樣用的。