power_supply子系統筆記

現在的手機平板供電系統變得比以前的嵌入式設備複雜了,要考慮USB、AC和battery的供電,同時USB和AC還要充電,這一系列功能一般由電源管理芯片完成。android設備使用的供電系統使用的是sys文件系統的固定位置,對應的硬件抽象層是android標準的直接接口,內核中使用的是power_supply框架,對芯片驅動填充好power_supply結構體,再進行註冊即可。而我們在開發android設備初期的時候,充電和電量計給我們帶來不小的麻煩,常見麻煩是:I2C通信失敗(I2C總線上設備上電邏輯問題),電量顯示不準(I2C讀寫函數處理問題),充電電流電壓設置等。


概況

下面先給給感性認識的圖:下圖是從網上看到的結構圖,此處我們重點在驅動層。


下圖是華爲mate7供電系統類型:


下圖是華爲mate7電池系統屬性和uevent上報的值:



問:android怎麼知道當前是什麼供電,充電中與否?

答:uevent機制(實質是net_link方式的socket)(廣泛應用於hotplug),充電插入與斷開時,內核通過發送uevent信息,告訴android。

問:android如何知道各種參數並更新的?

答:通過kobject_uevent發送通知給上層,上層讀取sys相關文件屬性


驅動框架

power_supply驅動頭文件#include <linux/power_supply.h>

框架核心文件:

ccflags-$(CONFIG_POWER_SUPPLY_DEBUG) := -DDEBUG

power_supply-y				:= power_supply_core.o
power_supply-$(CONFIG_SYSFS)		+= power_supply_sysfs.o
power_supply-$(CONFIG_LEDS_TRIGGERS)	+= power_supply_leds.o

obj-$(CONFIG_POWER_SUPPLY)	+= power_supply.o

由Makefile可見,power_supply.o目標是由3個核心文件組成。

power_supply_core.c主要提供psy的註冊/註銷,psy基本屬性的設置(調用的是註冊時傳入的函數指針回調進行設置),還有供電改變power_supply_changed發送kobject_uevent給android的HAL層。

power_supply_sysfs.c顧名思義就是負責sysfs文件系統下的屬性文件,主要負責,power_supply下的各種屬性結點的show和store,還有填充uevent的信息。

power_supply_leds.c提供了充電,點亮滿等trigger的註冊,還有根據電池不同狀態和容量時,執行LED動作。


struct power_supply是註冊LED設備的基本數據結構,在註冊的時候,需要適當填充好該結構體成員。

struct power_supply {
	const char *name;	//LED名稱
	enum power_supply_type type;	//供電類型,USB,battery,適配器?
	enum power_supply_property *properties;	//該供電支持的屬性數組
	size_t num_properties;	//該供電屬性個數

	char **supplied_to;		//可對什麼供電,對系統還是OTG對外部設備
	size_t num_supplicants;	//
	/* psy的num_properties個屬性的獲取/設置 */
	int (*get_property)(struct power_supply *psy,
			    enum power_supply_property psp,
			    union power_supply_propval *val);
	int (*set_property)(struct power_supply *psy,
			    enum power_supply_property psp,
			    const union power_supply_propval *val);
	int (*property_is_writeable)(struct power_supply *psy,	//屬性psp是否可寫,影響是否mode |= S_IWUSR,可選
				     enum power_supply_property psp);
	void (*external_power_changed)(struct power_supply *psy);//供電改變的回調函數,可選
	void (*set_charged)(struct power_supply *psy);

	/* For APM emulation, think legacy userspace. */
	int use_for_apm;

	/* private */
	struct device *dev;
	struct work_struct changed_work;
	spinlock_t changed_lock;
	bool changed;
	struct wake_lock work_wake_lock;
	/* 充電中,充滿等狀態關聯的LED */
#ifdef CONFIG_LEDS_TRIGGERS
	struct led_trigger *charging_full_trig;
	char *charging_full_trig_name;
	struct led_trigger *charging_trig;
	char *charging_trig_name;
	struct led_trigger *full_trig;
	char *full_trig_name;
	struct led_trigger *online_trig;
	char *online_trig_name;
	struct led_trigger *charging_blink_full_solid_trig;
	char *charging_blink_full_solid_trig_name;
#endif
};

該結構體中提供幾個properties,你的get_property函數就得提供幾個對應的get操作值,set_property函數一般用的不多(如用的話,property_is_writeable函數需要提供對應屬性需要能return 1);

external_power_changed函數指針會在power_supply_changed被外部調用時執行;

set_charged函數指針會在供電爲電池類型驅動中調用power_supply_set_battery_charged時執行;


至於其中的led部分要加入config選項,

使用LED的trigger,你的LED設備也需要註冊,詳見我的LED子系統文章。當你註冊psy時,power_supply_register會去調用power_supply_create_triggers去創建trigger(battery類型的psy創建"%s-charging-or-full"(%s代表psy->name下同),"%s-charging","%s-full","%s-charging-blink-full-solid"四個trigger;而其他類型的psy只建立"%s-online"一個trigger),之後你在註冊psy設備時,

在調用void power_supply_update_leds(struct power_supply *psy)時會觸發去執行關聯的LED設備。


以現在開發的平板充電爲例:充電芯片bq24296,通過USB和AC適配器(USB-WALL)充電。

static int bq2429x_usb_get_property(struct power_supply *psy,
				enum power_supply_property psp,
				union power_supply_propval *val)
{
	struct bq2429x *bq = container_of(psy, struct bq2429x, usb);

	switch (psp) {
	case POWER_SUPPLY_PROP_ONLINE:
		if (bq2429x_get_vbus_type(bq) == BQ2429X_VBUS_USB_CHARGER)
			val->intval = 1;
		else
			val->intval = 0;
		break;
	case POWER_SUPPLY_PROP_CHARGE_TYPE:
		val->intval = bq2429x_charge_type(bq);
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static enum power_supply_property bq2429x_charger_props[] = {
	POWER_SUPPLY_PROP_ONLINE,	//外充電,哪個在線,cat屬性值在線爲1,否則0
	POWER_SUPPLY_PROP_CHARGE_TYPE,//充電類型,USB還是WALL
};

static int bq2429x_psy_register(struct bq2429x *bq)
{
	int ret;

	bq->usb.name = "bq2429x-usb";	//在power_supply下顯示的名字
	bq->usb.type = POWER_SUPPLY_TYPE_USB;//該充電類型
	bq->usb.properties = bq2429x_charger_props;//該供電支持的屬性
	bq->usb.num_properties = ARRAY_SIZE(bq2429x_charger_props);ni 
	bq->usb.get_property = bq2429x_usb_get_property;//獲取屬性值
	bq->usb.external_power_changed = NULL;

	ret = power_supply_register(bq->dev, &bq->usb);
	if (ret) {
		dev_err(bq->dev, "failed to register usb: %d\n", ret);
		return ret;
	}

	bq->wall.name = "bq2429x-wall";
	bq->wall.type = POWER_SUPPLY_TYPE_MAINS;
	bq->wall.properties = bq2429x_charger_props;
	bq->wall.num_properties = ARRAY_SIZE(bq2429x_charger_props);
	bq->wall.get_property = bq2429x_wall_get_property;
	bq->wall.external_power_changed = NULL;

	ret = power_supply_register(bq->dev, &bq->wall);
	if (ret) {
		dev_err(bq->dev, "failed to register wall: %d\n", ret);
		goto err_psy_wall;
	}

	return 0;

err_psy_wall:
	power_supply_unregister(&bq->usb);
	return ret;
}

兩種供電註冊方式如上,至於如何設置充電輸入輸出的電壓電流大小,一般應芯片而異,此處bq24296充電芯片會根據插入的供電類型,發送一箇中斷給CPU,CPU再去讀取充電芯片的對應寄存器判斷充電類型,再去設置充電參數,這個一般在插入充電器線的時候就完成了。這段代碼之後,在機器中顯示效果如下:


平板的電池電量爲例:bq27541爲電池電量管理芯片,android手機電池狀態信息如電量電壓都是來自這的。

static enum power_supply_property msm_bq_power_props[] = {
	POWER_SUPPLY_PROP_CAPACITY,
	POWER_SUPPLY_PROP_VOLTAGE_NOW,
};

static int bq_power_get_property(struct power_supply *psy,
					enum power_supply_property psp,
					union power_supply_propval *val)
{
	switch (psp) {
	case POWER_SUPPLY_PROP_CAPACITY:	//電量百分比
		val->intval = bq27541_get_battery_capacity();
		break;
	case POWER_SUPPLY_PROP_VOLTAGE_NOW:	//電壓,單位uV
		val->intval = bq27541_get_battery_mvolts();
		break;

	default:
		return -EINVAL;
	}
	return 0;
}

static int __devinit bq27541_register_psy(struct bq27541_device_info *bq27541_dev)
{
	int retval;

	/* setup & register the battery power supply */
	bq27541_dev->bq_psy.name	= "bq";
	bq27541_dev->bq_psy.type	= POWER_SUPPLY_TYPE_BATTERY;	//供電類型battery
	bq27541_dev->bq_psy.num_supplicants = 0;
	bq27541_dev->bq_psy.properties = msm_bq_power_props;
	bq27541_dev->bq_psy.num_properties = ARRAY_SIZE(msm_bq_power_props);
	bq27541_dev->bq_psy.get_property = bq_power_get_property;

	retval =  power_supply_register(&bq27541_dev->client->dev, &bq27541_dev->bq_psy);

	if (retval < 0) {
		pr_err("power_supply_register bq failed rc = %d\n", retval);
		goto unregister_bq;
	}

	return 0;

unregister_bq:
	power_supply_unregister(&bq27541_dev->bq_psy);

	return retval;
}


以上代碼是註冊電池供電設備,此處只支持上報電池容量和電池電壓,在系統中顯示如下:


不管是充電還是電池,android都是通過屬性的讀寫來完成的,它會掃描/sys/class/power_supply下的所有文件,並按照type值來分類爲USB,Battery還是MAINS等,這樣android就可以直接讀取電池相關信息。但是如果有2個電池power_supply我還沒看過上層是以誰爲準,第一個麼?,有空再看看。


那麼USB插上,拔下或者狀態的改變到底是如何讓android知道的呢?

前面提到了用uevent的確是這樣。power_supply_core.c提供了函數void power_supply_changed(struct power_supply *psy),這個函數調用後,就會把當前psy屬性的所有值發送給android。

那麼對於不同的psy發送的附加內容什麼,誰填充的,填充的格式是什麼?

在power_supply_sysfs.c中,power_supply_uevent函數負責填充,填充格式由其下函數add_uevent_var完成,當你調用power_supply_changed,就會把你當前psy的所有屬性值發送給android的HAL,uevent相關函數代碼片如下:

//以下2個函數是power_supply_core.c中的函數
static int __init power_supply_class_init(void)
{
	power_supply_class = class_create(THIS_MODULE, "power_supply");

	if (IS_ERR(power_supply_class))
		return PTR_ERR(power_supply_class);

	power_supply_class->dev_uevent = power_supply_uevent;//設備填充env的函數指針
	power_supply_init_attrs(&power_supply_dev_type);

	return 0;
}
	//供電狀態改變的work
static void power_supply_changed_work(struct work_struct *work)
{
	unsigned long flags;
	struct power_supply *psy = container_of(work, struct power_supply,
						changed_work);

	dev_dbg(psy->dev, "%s\n", __func__);

	spin_lock_irqsave(&psy->changed_lock, flags);
	if (psy->changed) {
		psy->changed = false;
		spin_unlock_irqrestore(&psy->changed_lock, flags);

		class_for_each_device(power_supply_class, NULL, psy,
				      __power_supply_changed_work);

		power_supply_update_leds(psy);

		kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);//發送uevent給android的HAL層
		spin_lock_irqsave(&psy->changed_lock, flags);
	}
	if (!psy->changed)
		wake_unlock(&psy->work_wake_lock);
	spin_unlock_irqrestore(&psy->changed_lock, flags);
}


//power_supply_sysfs.c中的uevent填充env函數
int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env)
{
	struct power_supply *psy = dev_get_drvdata(dev);
	int ret = 0, j;
	char *prop_buf;
	char *attrname;

	dev_dbg(dev, "uevent\n");

	if (!psy || !psy->dev) {
		dev_dbg(dev, "No power supply yet\n");
		return ret;
	}

	dev_dbg(dev, "POWER_SUPPLY_NAME=%s\n", psy->name);

	ret = add_uevent_var(env, "POWER_SUPPLY_NAME=%s", psy->name);	//例如 POWER_SUPPLY_NAME=bq2429x-usb
	if (ret)
		return ret;

	prop_buf = (char *)get_zeroed_page(GFP_KERNEL);
	if (!prop_buf)
		return -ENOMEM;

	for (j = 0; j < psy->num_properties; j++) {
		struct device_attribute *attr;
		char *line;

		attr = &power_supply_attrs[psy->properties[j]];

		ret = power_supply_show_property(dev, attr, prop_buf);
		if (ret == -ENODEV || ret == -ENODATA) {
			/* When a battery is absent, we expect -ENODEV. Don't abort;
			   send the uevent with at least the the PRESENT=0 property */
			ret = 0;
			continue;
		}

		if (ret < 0)
			goto out;

		line = strchr(prop_buf, '\n');
		if (line)
			*line = 0;

		attrname = kstruprdup(attr->attr.name, GFP_KERNEL);
		if (!attrname) {
			ret = -ENOMEM;
			goto out;
		}

		dev_dbg(dev, "prop %s=%s\n", attrname, prop_buf);
		/* 下面根據屬性個數,env可能填充的例如
			POWER_SUPPLY_ONLINE=1
			POWER_SUPPLY_CHARGE_TYPE=Fast
		*/
		ret = add_uevent_var(env, "POWER_SUPPLY_%s=%s", attrname, prop_buf);
		kfree(attrname);
		if (ret)
			goto out;
	}

out:
	free_page((unsigned long)prop_buf);

	return ret;
}


power_supply發送的uevent都是KOBJ_CHANGE,其它定義如下:

static const char *kobject_actions[] = {
       [KOBJ_ADD] =            "add",
       [KOBJ_REMOVE] =           "remove",
       [KOBJ_CHANGE] =            "change",
       [KOBJ_MOVE] =         "move",
       [KOBJ_ONLINE] =             "online",
       [KOBJ_OFFLINE] =     "offline",
};





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章