漫谈android系统(5)点亮LED

LED indicator 简介

所谓的LED indicator就是手机上面充电发短信的灯,有时会亮绿灯,有时会亮红灯,有时一起亮(橙灯),主要用于提醒用户电量、短信、电话。

bring up LED

要想点亮LED,就要了解其电路。
以下是我工作中案子的led。

VPH_PWR是系统默认电。处于高电平状态。

这里可以看到2个LED受到pmi8953的mpp2\mmp4控制。下面重要的是如何将mmp2\mpp4置起来。

bring up LED的步骤

现在我们基本上是采用pwm来控制LED。那么我们是如何来控制的呢?

通过soc->pmi8952(内部pwm)->mpp2/mpp4的方式去控制,那么咋么控制,首先你得懂得如何通过它的协议对这2pin做操作的。

根据pmi8952的寄存器描述文件,可以知道pmi8952这颗ic是挂在spi总线上的。

以下是我大致总结出来的步骤

  • 查看spec确定其如何IC如何挂载,mpp2/mpp4的地址与描述、
  • 确定led是如何初始化的
  • debug方式点亮LED,保证硬件的可靠性
  • led是如何控制mpp2/mpp4的
  • pmi8952的内部寄存器是如何控制pwm的
  • 修改dtsi
dtsi修改

具体的dtsi在这里展示。以供后面解说为何要这样做!

qcom,leds@a100 {
			status = "okay";
			compatible = "qcom,leds-qpnp";
			reg = <0xa100 0x100>;
			label = "mpp";
			qcom,led_mpp_2{
				label = "mpp";
				linux,name = "red";
				linux,default-trigger = "none";
				qcom,default-state = "off";
				qcom,max-current = <40>;
				qcom,current-setting = <5>;
				qcom,id = <6>;
				qcom,mode = "pwm";
				qcom,source-sel = <8>;
				qcom,mode-ctrl = <0x60>;
				qcom,vin-ctrl=<3>;
				pwms = <&pmi8950_pwm 0 0>;
				qcom,pwm-us = <100>; //setup 0.0001s 
			};
		};
		qcom,leds@a300 {
			status = "okay";
			compatible = "qcom,leds-qpnp";
			reg = <0xa300 0x100>;
			label = "mpp";
			qcom,led_mpp_2{
				label = "mpp";
				linux,name = "green";
				linux,default-trigger = "none";
				qcom,default-state = "off";
				qcom,max-current = <40>;
				qcom,current-setting = <5>;
				qcom,id = <6>;
				qcom,mode = "pwm";
				qcom,source-sel = <8>;
				qcom,mode-ctrl = <0x60>;
				qcom,vin-ctrl=<3>;
				pwms = <&pmi8950_pwm 0 0>;
				qcom,pwm-us = <100>; //setup 0.0001s 
			};
		};

LED初始化启动

在android源码/kernel/driver/leds/led_qpnp.c就可以看出它是如何做初始化的动作的。

下面是led driver 的devices tree

static struct spmi_driver qpnp_leds_driver = {
	.driver		= {
		.name	= "qcom,leds-qpnp",
		.of_match_table = spmi_match_table,
	},
	.probe		= qpnp_leds_probe,
	.remove		= qpnp_leds_remove,
};
static int __init qpnp_led_init(void)
{
	return spmi_driver_register(&qpnp_leds_driver);
}
module_init(qpnp_led_init);

可以看到其name为qcom,leds-qpnp,那么它将会去找dts中的qcom,leds-qpnp字段做解析动作

probe函数:
//当传进来的值就已经是qcom,leds-qpnp了
static int qpnp_leds_probe(struct spmi_device *spmi)
{
	struct qpnp_led_data *led, *led_array;
	struct resource *led_resource;
	struct device_node *node, *temp;
	int rc, i, num_leds = 0, parsed_leds = 0;
	const char *led_label;
	bool regulator_probe = false;
	
	printk("[LED] qpnp_leds_probe\n");	
	//获取LEDdevices tree spi节点并且遍历节点+++++
	node = spmi->dev.of_node;
	if (node == NULL)
		return -ENODEV;

	temp = NULL;
	while ((temp = of_get_next_child(node, temp)))
		num_leds++;

	if (!num_leds)
		return -ECHILD;
//为每一个led节点分配空间,资源等等
	led_array = devm_kzalloc(&spmi->dev,
		(sizeof(struct qpnp_led_data) * num_leds), GFP_KERNEL);
	if (!led_array) {
		dev_err(&spmi->dev, "Unable to allocate memory\n");
		return -ENOMEM;
	}

	for_each_child_of_node(node, temp) {
		led = &led_array[parsed_leds];
		led->num_leds = num_leds;
		led->spmi_dev = spmi;

		led_resource = spmi_get_resource(spmi, NULL, IORESOURCE_MEM, 0);
		if (!led_resource) {
			dev_err(&spmi->dev, "Unable to get LED base address\n");
			rc = -ENXIO;
			goto fail_id_check;
		}
		led->base = led_resource->start;

		rc = of_property_read_string(temp, "label", &led_label);//找到dtsi的label项给变量led_label
		if (rc < 0) {
			dev_err(&led->spmi_dev->dev,
				"Failure reading label, rc = %d\n", rc);
			goto fail_id_check;
		}

		rc = of_property_read_string(temp, "linux,name",
			&led->cdev.name);//找到dtsi的linux,name项给变量led->cdev.name
		if (rc < 0) {
			dev_err(&led->spmi_dev->dev,
				"Failure reading led name, rc = %d\n", rc);
			goto fail_id_check;
		}
		printk("[LED] linux,name : %s\n", led->cdev.name);

		if(!strcmp(led->cdev.name,"button-backlight"))//判断其是不是虚拟按键的led
		{
			printk("[LED] : Copy Led structure\n");
			copy_led = led;
		}
		
		rc = of_property_read_u32(temp, "qcom,max-current",
			&led->max_current);//读取最大电量
		if (rc < 0) {
			dev_err(&led->spmi_dev->dev,
				"Failure reading max_current, rc =  %d\n", rc);
			goto fail_id_check;
		}

		rc = of_property_read_u32(temp, "qcom,id", &led->id);//读取id,如果是led indicator一般为6.具体为何是6后面分析
		if (rc < 0) {
			dev_err(&led->spmi_dev->dev,
				"Failure reading led id, rc =  %d\n", rc);
			goto fail_id_check;
		}

		rc = qpnp_get_common_configs(led, temp);//这个函数对我们不大
		if (rc) {
			dev_err(&led->spmi_dev->dev,
				"Failure reading common led configuration," \
				" rc = %d\n", rc);
			goto fail_id_check;
		}

		led->cdev.brightness_set    = qpnp_led_set;
		led->cdev.brightness_get    = qpnp_led_get;
//比对led_label,很显然我们走的是mpp方式,具体他如何实现的,我也将贴出来,但在此不做详解
		if (strncmp(led_label, "wled", sizeof("wled")) == 0) {
			rc = qpnp_get_config_wled(led, temp);
			if (rc < 0) {
				dev_err(&led->spmi_dev->dev,
					"Unable to read wled config data\n");
				goto fail_id_check;
			}
		} else if (strncmp(led_label, "flash", sizeof("flash"))
				== 0) {
			if (!of_find_property(node, "flash-boost-supply", NULL))
				regulator_probe = true;
			rc = qpnp_get_config_flash(led, temp, &regulator_probe);
			if (rc < 0) {
				dev_err(&led->spmi_dev->dev,
					"Unable to read flash config data\n");
				goto fail_id_check;
			}
		} else if (strncmp(led_label, "rgb", sizeof("rgb")) == 0) {
			rc = qpnp_get_config_rgb(led, temp);
			if (rc < 0) {
				dev_err(&led->spmi_dev->dev,
					"Unable to read rgb config data\n");
				goto fail_id_check;
			}
		} else if (strncmp(led_label, "mpp", sizeof("mpp")) == 0) {
			rc = qpnp_get_config_mpp(led, temp);
			if (rc < 0) {
				dev_err(&led->spmi_dev->dev,
						"Unable to read mpp config data\n");
				goto fail_id_check;
			}
		} else if (strcmp(led_label, "gpio") == 0) {
			rc = qpnp_get_config_gpio(led, temp);
			if (rc < 0) {
				dev_err(&led->spmi_dev->dev,
						"Unable to read gpio config data\n");
				goto fail_id_check;
			}
		} else if (strncmp(led_label, "kpdbl", sizeof("kpdbl")) == 0) {
			bitmap_zero(kpdbl_leds_in_use, NUM_KPDBL_LEDS);
			is_kpdbl_master_turn_on = false;
			rc = qpnp_get_config_kpdbl(led, temp);
			if (rc < 0) {
				dev_err(&led->spmi_dev->dev,
					"Unable to read kpdbl config data\n");
				goto fail_id_check;
			}
		} else {
			dev_err(&led->spmi_dev->dev, "No LED matching label\n");
			rc = -EINVAL;
			goto fail_id_check;
		}
//既不是flash led那么在此会新建锁,并作work queue的动作
		if (led->id != QPNP_ID_FLASH1_LED0 &&
					led->id != QPNP_ID_FLASH1_LED1)
			mutex_init(&led->lock);

		led->in_order_command_processing = of_property_read_bool
				(temp, "qcom,in-order-command-processing");

		if (led->in_order_command_processing) {
			/*
			 * the command order from user space needs to be
			 * maintained use ordered workqueue to prevent
			 * concurrency
			 */
			led->workqueue = alloc_ordered_workqueue
							("led_workqueue", 0);
			if (!led->workqueue) {
				rc = -ENOMEM;
				goto fail_id_check;
			}
		}

		INIT_WORK(&led->work, qpnp_led_work);
//可以初始化led
		rc =  qpnp_led_initialize(led);
		if (rc < 0)
			goto fail_id_check;

		rc = qpnp_led_set_max_brightness(led);
		if (rc < 0)
			goto fail_id_check;

		rc = led_classdev_register(&spmi->dev, &led->cdev);
		if (rc) {
			dev_err(&spmi->dev, "unable to register led %d,rc=%d\n",
						 led->id, rc);
			goto fail_id_check;
		}

		if (led->id == QPNP_ID_FLASH1_LED0 ||
			led->id == QPNP_ID_FLASH1_LED1) {
			rc = sysfs_create_group(&led->cdev.dev->kobj,
							&led_attr_group);
			if (rc)
				goto fail_id_check;

		}
//看mpp进入哪种mode,显然选择的是pwm
		if (led->id == QPNP_ID_LED_MPP) {
			if (!led->mpp_cfg->pwm_cfg)
				break;
			if (led->mpp_cfg->pwm_cfg->mode == PWM_MODE) {
				rc = sysfs_create_group(&led->cdev.dev->kobj,
					&pwm_attr_group);
				if (rc)
					goto fail_id_check;
			}
			if (led->mpp_cfg->pwm_cfg->use_blink) {
				rc = sysfs_create_group(&led->cdev.dev->kobj,
					&blink_attr_group);
				if (rc)
					goto fail_id_check;

				rc = sysfs_create_group(&led->cdev.dev->kobj,
					&lpg_attr_group);
				if (rc)
					goto fail_id_check;
			} else if (led->mpp_cfg->pwm_cfg->mode == LPG_MODE) {
				rc = sysfs_create_group(&led->cdev.dev->kobj,
					&lpg_attr_group);
				if (rc)
					goto fail_id_check;
			}
		} else if ((led->id == QPNP_ID_RGB_RED) ||
			(led->id == QPNP_ID_RGB_GREEN) ||
			(led->id == QPNP_ID_RGB_BLUE)) {
			if (led->rgb_cfg->pwm_cfg->mode == PWM_MODE) {
				rc = sysfs_create_group(&led->cdev.dev->kobj,
					&pwm_attr_group);
				if (rc)
					goto fail_id_check;
			}
			if (led->rgb_cfg->pwm_cfg->use_blink) {
				rc = sysfs_create_group(&led->cdev.dev->kobj,
					&blink_attr_group);
				if (rc)
					goto fail_id_check;

				rc = sysfs_create_group(&led->cdev.dev->kobj,
					&lpg_attr_group);
				if (rc)
					goto fail_id_check;
			} else if (led->rgb_cfg->pwm_cfg->mode == LPG_MODE) {
				rc = sysfs_create_group(&led->cdev.dev->kobj,
					&lpg_attr_group);
				if (rc)
					goto fail_id_check;
			}
		} else if (led->id == QPNP_ID_KPDBL) {
			if (led->kpdbl_cfg->pwm_cfg->mode == PWM_MODE) {
				rc = sysfs_create_group(&led->cdev.dev->kobj,
					&pwm_attr_group);
				if (rc)
					goto fail_id_check;
			}
			if (led->kpdbl_cfg->pwm_cfg->use_blink) {
				rc = sysfs_create_group(&led->cdev.dev->kobj,
					&blink_attr_group);
				if (rc)
					goto fail_id_check;

				rc = sysfs_create_group(&led->cdev.dev->kobj,
					&lpg_attr_group);
				if (rc)
					goto fail_id_check;
			} else if (led->kpdbl_cfg->pwm_cfg->mode == LPG_MODE) {
				rc = sysfs_create_group(&led->cdev.dev->kobj,
					&lpg_attr_group);
				if (rc)
					goto fail_id_check;
			}
		}

		/* configure default state */
		if (led->default_on) {
			led->cdev.brightness = led->cdev.max_brightness;
			__qpnp_led_work(led, led->cdev.brightness);
			if (led->turn_off_delay_ms > 0)
				qpnp_led_turn_off(led);
		} else
			led->cdev.brightness = LED_OFF;

		parsed_leds++;
	}
	dev_set_drvdata(&spmi->dev, led_array);
	return 0;

fail_id_check:
	for (i = 0; i < parsed_leds; i++) {
		if (led_array[i].id != QPNP_ID_FLASH1_LED0 &&
				led_array[i].id != QPNP_ID_FLASH1_LED1)
			mutex_destroy(&led_array[i].lock);
		if (led_array[i].in_order_command_processing)
			destroy_workqueue(led_array[i].workqueue);
		led_classdev_unregister(&led_array[i].cdev);
	}

	return rc;
}

以下是led_label选择为mpp的函数配置,其道理还是和前面的一致。

static int qpnp_get_config_mpp(struct qpnp_led_data *led,
		struct device_node *node)
{
	int rc;
	u32 val;
	u8 led_mode;
	const char *mode;

	led->mpp_cfg = devm_kzalloc(&led->spmi_dev->dev,
			sizeof(struct mpp_config_data), GFP_KERNEL);
	if (!led->mpp_cfg) {
		dev_err(&led->spmi_dev->dev, "Unable to allocate memory\n");
		return -ENOMEM;
	}

	if (of_find_property(of_get_parent(node), "mpp-power-supply", NULL)) {
		led->mpp_cfg->mpp_reg =
				regulator_get(&led->spmi_dev->dev,
							"mpp-power");
		if (IS_ERR(led->mpp_cfg->mpp_reg)) {
			rc = PTR_ERR(led->mpp_cfg->mpp_reg);
			dev_err(&led->spmi_dev->dev,
				"MPP regulator get failed(%d)\n", rc);
			return rc;
		}
	}

	if (led->mpp_cfg->mpp_reg) {
		rc = of_property_read_u32(of_get_parent(node),
					"qcom,mpp-power-max-voltage", &val);
		if (!rc)
			led->mpp_cfg->max_uV = val;
		else
			goto err_config_mpp;

		rc = of_property_read_u32(of_get_parent(node),
					"qcom,mpp-power-min-voltage", &val);
		if (!rc)
			led->mpp_cfg->min_uV = val;
		else
			goto err_config_mpp;

	} else {
		rc = of_property_read_u32(of_get_parent(node),
					"qcom,mpp-power-max-voltage", &val);
		if (!rc)
			dev_warn(&led->spmi_dev->dev,
						"No regulator specified\n");

		rc = of_property_read_u32(of_get_parent(node),
					"qcom,mpp-power-min-voltage", &val);
		if (!rc)
			dev_warn(&led->spmi_dev->dev,
						"No regulator specified\n");
	}

	led->mpp_cfg->current_setting = LED_MPP_CURRENT_MIN;
	rc = of_property_read_u32(node, "qcom,current-setting", &val);
	if (!rc) {
		if (led->mpp_cfg->current_setting < LED_MPP_CURRENT_MIN)
			led->mpp_cfg->current_setting = LED_MPP_CURRENT_MIN;
		else if (led->mpp_cfg->current_setting > LED_MPP_CURRENT_MAX)
			led->mpp_cfg->current_setting = LED_MPP_CURRENT_MAX;
		else
			led->mpp_cfg->current_setting = (u8) val;
	} else if (rc != -EINVAL)
		goto err_config_mpp;

	led->mpp_cfg->source_sel = LED_MPP_SOURCE_SEL_DEFAULT;
	rc = of_property_read_u32(node, "qcom,source-sel", &val);
	if (!rc)
		led->mpp_cfg->source_sel = (u8) val;
	else if (rc != -EINVAL)
		goto err_config_mpp;

	led->mpp_cfg->mode_ctrl = LED_MPP_MODE_SINK;
	rc = of_property_read_u32(node, "qcom,mode-ctrl", &val);
	if (!rc)
		led->mpp_cfg->mode_ctrl = (u8) val;
	else if (rc != -EINVAL)
		goto err_config_mpp;

	led->mpp_cfg->vin_ctrl = LED_MPP_VIN_CTRL_DEFAULT;
	rc = of_property_read_u32(node, "qcom,vin-ctrl", &val);
	if (!rc)
		led->mpp_cfg->vin_ctrl = (u8) val;
	else if (rc != -EINVAL)
		goto err_config_mpp;

	led->mpp_cfg->min_brightness = 0;
	rc = of_property_read_u32(node, "qcom,min-brightness", &val);
	if (!rc)
		led->mpp_cfg->min_brightness = (u8) val;
	else if (rc != -EINVAL)
		goto err_config_mpp;

	rc = of_property_read_string(node, "qcom,mode", &mode);
	if (!rc) {
		led_mode = qpnp_led_get_mode(mode);
		led->mpp_cfg->pwm_mode = led_mode;
		if (led_mode == MANUAL_MODE)
			return MANUAL_MODE;
		else if (led_mode == -EINVAL) {
			dev_err(&led->spmi_dev->dev, "Selected mode not " \
				"supported for mpp.\n");
			rc = -EINVAL;
			goto err_config_mpp;
		}
		led->mpp_cfg->pwm_cfg = devm_kzalloc(&led->spmi_dev->dev,
					sizeof(struct pwm_config_data),
					GFP_KERNEL);
		if (!led->mpp_cfg->pwm_cfg) {
			dev_err(&led->spmi_dev->dev,
				"Unable to allocate memory\n");
			rc = -ENOMEM;
			goto err_config_mpp;
		}
		led->mpp_cfg->pwm_cfg->mode = led_mode;
		led->mpp_cfg->pwm_cfg->default_mode = led_mode;
	} else
		return rc;

	rc = qpnp_get_config_pwm(led->mpp_cfg->pwm_cfg, led->spmi_dev, node);
	if (rc < 0)
		goto err_config_mpp;
	return 0;
err_config_mpp:
	if (led->mpp_cfg->mpp_reg)
		regulator_put(led->mpp_cfg->mpp_reg);
	return rc;
}
remove函数

该函数的主要工作就是去掉work queue的动作释放内存,在此便不在详细解释了。

static int qpnp_leds_remove(struct spmi_device *spmi)
{
	struct qpnp_led_data *led_array  = dev_get_drvdata(&spmi->dev);
	int i, parsed_leds = led_array->num_leds;

	for (i = 0; i < parsed_leds; i++) {
		cancel_work_sync(&led_array[i].work);
		if (led_array[i].id != QPNP_ID_FLASH1_LED0 &&
				led_array[i].id != QPNP_ID_FLASH1_LED1)
			mutex_destroy(&led_array[i].lock);

		if (led_array[i].in_order_command_processing)
			destroy_workqueue(led_array[i].workqueue);
		led_classdev_unregister(&led_array[i].cdev);
		switch (led_array[i].id) {
		case QPNP_ID_WLED:
			break;
		case QPNP_ID_FLASH1_LED0:
		case QPNP_ID_FLASH1_LED1:
			if (led_array[i].flash_cfg->flash_reg_get)
				regulator_put(led_array[i].flash_cfg-> \
							flash_boost_reg);
			if (led_array[i].flash_cfg->torch_enable)
				if (!led_array[i].flash_cfg->no_smbb_support)
					regulator_put(led_array[i].
					flash_cfg->torch_boost_reg);
			sysfs_remove_group(&led_array[i].cdev.dev->kobj,
							&led_attr_group);
			break;
		case QPNP_ID_RGB_RED:
		case QPNP_ID_RGB_GREEN:
		case QPNP_ID_RGB_BLUE:
			if (led_array[i].rgb_cfg->pwm_cfg->mode == PWM_MODE)
				sysfs_remove_group(&led_array[i].cdev.dev->\
					kobj, &pwm_attr_group);
			if (led_array[i].rgb_cfg->pwm_cfg->use_blink) {
				sysfs_remove_group(&led_array[i].cdev.dev->\
					kobj, &blink_attr_group);
				sysfs_remove_group(&led_array[i].cdev.dev->\
					kobj, &lpg_attr_group);
			} else if (led_array[i].rgb_cfg->pwm_cfg->mode\
					== LPG_MODE)
				sysfs_remove_group(&led_array[i].cdev.dev->\
					kobj, &lpg_attr_group);
			break;
		case QPNP_ID_LED_MPP:
			if (!led_array[i].mpp_cfg->pwm_cfg)
				break;
			if (led_array[i].mpp_cfg->pwm_cfg->mode == PWM_MODE)
				sysfs_remove_group(&led_array[i].cdev.dev->\
					kobj, &pwm_attr_group);
			if (led_array[i].mpp_cfg->pwm_cfg->use_blink) {
				sysfs_remove_group(&led_array[i].cdev.dev->\
					kobj, &blink_attr_group);
				sysfs_remove_group(&led_array[i].cdev.dev->\
					kobj, &lpg_attr_group);
			} else if (led_array[i].mpp_cfg->pwm_cfg->mode\
					== LPG_MODE)
				sysfs_remove_group(&led_array[i].cdev.dev->\
					kobj, &lpg_attr_group);
			if (led_array[i].mpp_cfg->mpp_reg)
				regulator_put(led_array[i].mpp_cfg->mpp_reg);
			break;
		case QPNP_ID_KPDBL:
			if (led_array[i].kpdbl_cfg->pwm_cfg->mode == PWM_MODE)
				sysfs_remove_group(&led_array[i].cdev.dev->
					kobj, &pwm_attr_group);
			if (led_array[i].kpdbl_cfg->pwm_cfg->use_blink) {
				sysfs_remove_group(&led_array[i].cdev.dev->
					kobj, &blink_attr_group);
				sysfs_remove_group(&led_array[i].cdev.dev->
					kobj, &lpg_attr_group);
			} else if (led_array[i].kpdbl_cfg->pwm_cfg->mode
					== LPG_MODE)
				sysfs_remove_group(&led_array[i].cdev.dev->
					kobj, &lpg_attr_group);
			break;
		default:
			dev_err(&led_array[i].spmi_dev->dev,
					"Invalid LED(%d)\n",
					led_array[i].id);
			return -EINVAL;
		}
	}

	return 0;
}
小结

从代码中可以看出led的初始化就是建立一个device tree然后做各种资源配置,而这些资源配置全部来自于dtsi,最后建立一个work queue进行工作。

debug模式下点亮led

其实高通不可能让你直接很好地完成一些事情,当你看完spec后,做的第一件事,就是通过高通给的debug方式进行分析,确保你的硬件没问题的情况下,做配置才是最安全的。

找bus

其实我们很容易找到高通将led挂在spi的哪个bus上了。

比如:

&spmi_bus {
            qcom.pmi8950@2

很明显可以看到应该是在bus2上

debug
cd /sys/kernel/debug/spmi/spmi-0/
/*1、mpp2*/
echo 0x2a100 > address
echo 0x100 > count
cat data
/*以上可以得到从地址0x2a100开始得到0x100个字节的数据*/
//enable mpp2
echo 0x2a146 > address
echo 0x80 > data
//查看地址0x2a108的状态是不是0x80?如果是 表示被置起来了
//set mpp2 output and set output 1
echo 0x2a140 > address
echo 0x11 > data
/*此时是将red led点亮*/

这个debug就是看spec轻易就能做到的。

mpp2/mpp4控制led

qpnp_led_initialize函数将唤起mpp的配置。

static int qpnp_led_initialize(struct qpnp_led_data *led)
{
	int rc = 0;

	switch (led->id) {
	case QPNP_ID_WLED:
		rc = qpnp_wled_init(led);
		if (rc)
			dev_err(&led->spmi_dev->dev,
				"WLED initialize failed(%d)\n", rc);
		break;
	case QPNP_ID_FLASH1_LED0:
	case QPNP_ID_FLASH1_LED1:
		rc = qpnp_flash_init(led);
		if (rc)
			dev_err(&led->spmi_dev->dev,
				"FLASH initialize failed(%d)\n", rc);
		break;
	case QPNP_ID_RGB_RED:
	case QPNP_ID_RGB_GREEN:
	case QPNP_ID_RGB_BLUE:
		rc = qpnp_rgb_init(led);
		if (rc)
			dev_err(&led->spmi_dev->dev,
				"RGB initialize failed(%d)\n", rc);
		break;
	case QPNP_ID_LED_MPP:
		rc = qpnp_mpp_init(led);
		if (rc)
			dev_err(&led->spmi_dev->dev,
				"MPP initialize failed(%d)\n", rc);
		break;
	case QPNP_ID_LED_GPIO:
		rc = qpnp_gpio_init(led);
		if (rc)
			dev_err(&led->spmi_dev->dev,
				"GPIO initialize failed(%d)\n", rc);
		break;
	case QPNP_ID_KPDBL:
		rc = qpnp_kpdbl_init(led);
		if (rc)
			dev_err(&led->spmi_dev->dev,
				"KPDBL initialize failed(%d)\n", rc);
		break;
	default:
		dev_err(&led->spmi_dev->dev, "Invalid LED(%d)\n", led->id);
		return -EINVAL;
	}

	return rc;
}

static int qpnp_get_common_configs(struct qpnp_led_data *led,
				struct device_node *node)
{
	int rc;
	u32 val;
	const char *temp_string;

	led->cdev.default_trigger = LED_TRIGGER_DEFAULT;
	rc = of_property_read_string(node, "linux,default-trigger",
		&temp_string);
	if (!rc)
		led->cdev.default_trigger = temp_string;
	else if (rc != -EINVAL)
		return rc;

	led->default_on = false;
	rc = of_property_read_string(node, "qcom,default-state",
		&temp_string);
	if (!rc) {
		if (strncmp(temp_string, "on", sizeof("on")) == 0)
			led->default_on = true;
	} else if (rc != -EINVAL)
		return rc;

	led->turn_off_delay_ms = 0;
	rc = of_property_read_u32(node, "qcom,turn-off-delay-ms", &val);
	if (!rc)
		led->turn_off_delay_ms = val;
	else if (rc != -EINVAL)
		return rc;

	return 0;
}
那么mpp会如何初始化呢?

又是根据dtsi进行配置。

static int qpnp_mpp_init(struct qpnp_led_data *led)
{
	int rc;
	u8 val;

//判断配置没有超过限定的最大电流40与最小电流5
	if (led->max_current < LED_MPP_CURRENT_MIN ||
		led->max_current > LED_MPP_CURRENT_MAX) {
		dev_err(&led->spmi_dev->dev,
			"max current for mpp is not valid\n");
		return -EINVAL;
	}

	val = (led->mpp_cfg->current_setting / LED_MPP_CURRENT_PER_SETTING) - 1;

	if (val < 0)
		val = 0;
//偏移量0x41的地方写入vin ctrl spec上有写明
	rc = qpnp_led_masked_write(led, LED_MPP_VIN_CTRL(led->base),
		LED_MPP_VIN_MASK, led->mpp_cfg->vin_ctrl);
	if (rc) {
		dev_err(&led->spmi_dev->dev,
			"Failed to write led vin control reg\n");
		return rc;
	}
//偏移量0x4c的地方写上sink ctrl
	rc = qpnp_led_masked_write(led, LED_MPP_SINK_CTRL(led->base),
		LED_MPP_SINK_MASK, val);
	if (rc) {
		dev_err(&led->spmi_dev->dev,
			"Failed to write sink control reg\n");
		return rc;
	}
//初始化pwm
	if (led->mpp_cfg->pwm_mode != MANUAL_MODE) {
		rc = qpnp_pwm_init(led->mpp_cfg->pwm_cfg, led->spmi_dev,
					led->cdev.name);
		if (rc) {
			dev_err(&led->spmi_dev->dev,
				"Failed to initialize pwm\n");
			return rc;
		}
	}

	return 0;

相应的work queue也将会对mpp进行相关的设置

static void __qpnp_led_work(struct qpnp_led_data *led,
				enum led_brightness value)
{
	int rc;

	if (led->id == QPNP_ID_FLASH1_LED0 || led->id == QPNP_ID_FLASH1_LED1)
		mutex_lock(&flash_lock);
	else
		mutex_lock(&led->lock);

	switch (led->id) {
	case QPNP_ID_WLED:
		rc = qpnp_wled_set(led);
		if (rc < 0)
			dev_err(&led->spmi_dev->dev,
				"WLED set brightness failed (%d)\n", rc);
		break;
	case QPNP_ID_FLASH1_LED0:
	case QPNP_ID_FLASH1_LED1:
		rc = qpnp_flash_set(led);
		if (rc < 0)
			dev_err(&led->spmi_dev->dev,
				"FLASH set brightness failed (%d)\n", rc);
		break;
	case QPNP_ID_RGB_RED:
	case QPNP_ID_RGB_GREEN:
	case QPNP_ID_RGB_BLUE:
		rc = qpnp_rgb_set(led);
		if (rc < 0)
			dev_err(&led->spmi_dev->dev,
				"RGB set brightness failed (%d)\n", rc);
		break;
	case QPNP_ID_LED_MPP:
		rc = qpnp_mpp_set(led);
		if (rc < 0)
			dev_err(&led->spmi_dev->dev,
					"MPP set brightness failed (%d)\n", rc);
		break;
	case QPNP_ID_LED_GPIO:
		rc = qpnp_gpio_set(led);
		if (rc < 0)
			dev_err(&led->spmi_dev->dev,
					"GPIO set brightness failed (%d)\n",
					rc);
		break;
	case QPNP_ID_KPDBL:
		rc = qpnp_kpdbl_set(led);
		if (rc < 0)
			dev_err(&led->spmi_dev->dev,
				"KPDBL set brightness failed (%d)\n", rc);
		break;
	default:
		dev_err(&led->spmi_dev->dev, "Invalid LED(%d)\n", led->id);
		break;
	}
	if (led->id == QPNP_ID_FLASH1_LED0 || led->id == QPNP_ID_FLASH1_LED1)
		mutex_unlock(&flash_lock);
	else
		mutex_unlock(&led->lock);

}
}

static int qpnp_mpp_set(struct qpnp_led_data *led)
{
	int rc;
	u8 val;
	int duty_us, duty_ns, period_us;
   //设置背光
	if (led->cdev.brightness) {
		if (led->mpp_cfg->mpp_reg && !led->mpp_cfg->enable) {
			rc = regulator_set_voltage(led->mpp_cfg->mpp_reg,
					led->mpp_cfg->min_uV,
					led->mpp_cfg->max_uV);
			if (rc) {
				dev_err(&led->spmi_dev->dev,
					"Regulator voltage set failed rc=%d\n",
									rc);
				return rc;
			}

			rc = regulator_enable(led->mpp_cfg->mpp_reg);
			if (rc) {
				dev_err(&led->spmi_dev->dev,
					"Regulator enable failed(%d)\n", rc);
				goto err_reg_enable;
			}
		}

		led->mpp_cfg->enable = true;

		if (led->cdev.brightness < led->mpp_cfg->min_brightness) {
			dev_warn(&led->spmi_dev->dev,
				"brightness is less than supported..." \
				"set to minimum supported\n");
			led->cdev.brightness = led->mpp_cfg->min_brightness;
		}
//设置pwm
		if (led->mpp_cfg->pwm_mode != MANUAL_MODE) {
			if (!led->mpp_cfg->pwm_cfg->blinking) {
				led->mpp_cfg->pwm_cfg->mode =
					led->mpp_cfg->pwm_cfg->default_mode;
				led->mpp_cfg->pwm_mode =
					led->mpp_cfg->pwm_cfg->default_mode;
			}
		}
		if (led->mpp_cfg->pwm_mode == PWM_MODE) {
			/*config pwm for brightness scaling*/
			period_us = led->mpp_cfg->pwm_cfg->pwm_period_us;
			if (period_us > INT_MAX / NSEC_PER_USEC) {
				duty_us = (period_us * led->cdev.brightness) /
					LED_FULL;
				rc = pwm_config_us(
					led->mpp_cfg->pwm_cfg->pwm_dev,
					duty_us,
					period_us);
			} else {
				duty_ns = ((period_us * NSEC_PER_USEC) /
					LED_FULL) * led->cdev.brightness;
				rc = pwm_config(
					led->mpp_cfg->pwm_cfg->pwm_dev,
					duty_ns,
					period_us * NSEC_PER_USEC);
			}
			if (rc < 0) {
				dev_err(&led->spmi_dev->dev, "Failed to " \
					"configure pwm for new values\n");
				goto err_mpp_reg_write;
			}
		}

		if (led->mpp_cfg->pwm_mode != MANUAL_MODE)
			pwm_enable(led->mpp_cfg->pwm_cfg->pwm_dev);
		else {
			if (led->cdev.brightness < LED_MPP_CURRENT_MIN)
				led->cdev.brightness = LED_MPP_CURRENT_MIN;
			else {
				/*
				 * PMIC supports LED intensity from 5mA - 40mA
				 * in steps of 5mA. Brightness is rounded to
				 * 5mA or nearest lower supported values
				 */
				led->cdev.brightness /= LED_MPP_CURRENT_MIN;
				led->cdev.brightness *= LED_MPP_CURRENT_MIN;
			}

			val = (led->cdev.brightness / LED_MPP_CURRENT_MIN) - 1;

			rc = qpnp_led_masked_write(led,
					LED_MPP_SINK_CTRL(led->base),
					LED_MPP_SINK_MASK, val);
			if (rc) {
				dev_err(&led->spmi_dev->dev,
					"Failed to write sink control reg\n");
				goto err_mpp_reg_write;
			}
		}

		val = (led->mpp_cfg->source_sel & LED_MPP_SRC_MASK) |
			(led->mpp_cfg->mode_ctrl & LED_MPP_MODE_CTRL_MASK);

		rc = qpnp_led_masked_write(led,
			LED_MPP_MODE_CTRL(led->base), LED_MPP_MODE_MASK,
			val);
		if (rc) {
			dev_err(&led->spmi_dev->dev,
					"Failed to write led mode reg\n");
			goto err_mpp_reg_write;
		}

		rc = qpnp_led_masked_write(led,
				LED_MPP_EN_CTRL(led->base), LED_MPP_EN_MASK,
				LED_MPP_EN_ENABLE);
		if (rc) {
			dev_err(&led->spmi_dev->dev,
					"Failed to write led enable " \
					"reg\n");
			goto err_mpp_reg_write;
		}
	} else {
		if (led->mpp_cfg->pwm_mode != MANUAL_MODE) {
			led->mpp_cfg->pwm_cfg->mode =
				led->mpp_cfg->pwm_cfg->default_mode;
			led->mpp_cfg->pwm_mode =
				led->mpp_cfg->pwm_cfg->default_mode;
			pwm_disable(led->mpp_cfg->pwm_cfg->pwm_dev);
		}
		rc = qpnp_led_masked_write(led,
					LED_MPP_MODE_CTRL(led->base),
					LED_MPP_MODE_MASK,
					LED_MPP_MODE_DISABLE);
		if (rc) {
			dev_err(&led->spmi_dev->dev,
					"Failed to write led mode reg\n");
			goto err_mpp_reg_write;
		}

		rc = qpnp_led_masked_write(led,
					LED_MPP_EN_CTRL(led->base),
					LED_MPP_EN_MASK,
					LED_MPP_EN_DISABLE);
		if (rc) {
			dev_err(&led->spmi_dev->dev,
					"Failed to write led enable reg\n");
			goto err_mpp_reg_write;
		}

		if (led->mpp_cfg->mpp_reg && led->mpp_cfg->enable) {
			rc = regulator_disable(led->mpp_cfg->mpp_reg);
			if (rc) {
				dev_err(&led->spmi_dev->dev,
					"MPP regulator disable failed(%d)\n",
					rc);
				return rc;
			}

			rc = regulator_set_voltage(led->mpp_cfg->mpp_reg,
						0, led->mpp_cfg->max_uV);
			if (rc) {
				dev_err(&led->spmi_dev->dev,
					"MPP regulator voltage set failed(%d)\n",
					rc);
				return rc;
			}
		}

		led->mpp_cfg->enable = false;
	}

	if (led->mpp_cfg->pwm_mode != MANUAL_MODE)
		led->mpp_cfg->pwm_cfg->blinking = false;
	qpnp_dump_regs(led, mpp_debug_regs, ARRAY_SIZE(mpp_debug_regs));

	return 0;

err_mpp_reg_write:
	if (led->mpp_cfg->mpp_reg)
		regulator_disable(led->mpp_cfg->mpp_reg);
err_reg_enable:
	if (led->mpp_cfg->mpp_reg)
		regulator_set_voltage(led->mpp_cfg->mpp_reg, 0,
							led->mpp_cfg->max_uV);
	led->mpp_cfg->enable = false;

	return rc;
}

它会在workqueue不停地去设置LED的状态。

pwm的设定

pmi8953内部只有一个pwm。因此需要公用一个pwm。pwm是如何设定,产生的将会在以后解释。

将DTEST1连接到pwm上

在kernel/driver/pwm/pwm-qpnp.c

它的probe函数中加入

static int qpnp_pwm_probe(struct spmi_device *spmi)
{
	struct qpnp_pwm_chip	*pwm_chip;
	int			rc;
	u8 value;
	
	printk("%s\n",__FUNCTION__);

	pwm_chip = kzalloc(sizeof(*pwm_chip), GFP_KERNEL);
	if (pwm_chip == NULL) {
		pr_err("kzalloc() failed.\n");
		return -ENOMEM;
	}

	spin_lock_init(&pwm_chip->lpg_lock);

	pwm_chip->spmi_dev = spmi;
	dev_set_drvdata(&spmi->dev, pwm_chip);

	rc = qpnp_parse_dt_config(spmi, pwm_chip);

	if (rc)
		goto failed_config;

	pwm_chip->chip.dev = &spmi->dev;
	pwm_chip->chip.ops = &qpnp_pwm_ops;
	pwm_chip->chip.base = -1;
	pwm_chip->chip.npwm = 1;

	rc = pwmchip_add(&pwm_chip->chip);
	if (rc < 0) {
		pr_err("pwmchip_add() failed: %d\n", rc);
		goto failed_insert;
	}

	if (pwm_chip->channel_owner)
		pwm_chip->chip.pwms[0].label = pwm_chip->channel_owner;
//++++++++++++++++++++	
	value=0xA5;
	spmi_ext_register_writel(pwm_chip->spmi_dev->ctrl, 3, 0xB0D0, &value, 1 );
	value=0x01;
	spmi_ext_register_writel(pwm_chip->spmi_dev->ctrl, 3, 0xB0E2, &value, 1 );
//--------------------
	return 0;

failed_insert:
	kfree(pwm_chip->lpg_config.lut_config.duty_pct_list);
failed_config:
	dev_set_drvdata(&spmi->dev, NULL);
	kfree(pwm_chip);
	return rc;
}

寄存器为何要如此写,这是开了case给高通才得知,这部分的内容高通没有任何spce。

取消pwm设备检查,使red与green le都可以使用pwm

在kernel/driver/pwm/core.c

static int pwm_device_request(struct pwm_device *pwm, const char *label)
{
	int err;

//  remove this flag to support led green and red request pwm devices twice
	//if (test_bit(PWMF_REQUESTED, &pwm->flags))
		//return -EBUSY;

	if (!try_module_get(pwm->chip->ops->owner))
		return -ENODEV;

	if (pwm->chip->ops->request) {
		err = pwm->chip->ops->request(pwm->chip, pwm);
		if (err) {
			module_put(pwm->chip->ops->owner);
			return err;
		}
	}

	set_bit(PWMF_REQUESTED, &pwm->flags);
	pwm->label = label;

	return 0;
}

配置dtsi

在源码中自带的文档就对这个配置说明了。

Optional properties for MPP LED:
- linux,default-trigger: trigger the led from external modules such as display
- qcom,default-state: default state of the led, should be "on" or "off"
- qcom,source-sel: select power source, default 1 (enabled)
- qcom,mode-ctrl: select operation mode, default 0x60 = Mode Sink
- qcom,mode: mode the led should operate in, options "pwm", "lpg" and "manual"
- qcom,vin-ctrl: select input source, supported values are 0 to 3
- qcom,use-blink: Use blink sysfs entry for switching into lpg mode.  For optimal use, set default mode to pwm.  All required lpg parameters must be supplied.
- qcom,min-brightness - Lowest possible brightness supported on this LED other than 0.
- qcom,current-setting: default current value for wled used as button backlight in mA
- mpp-power-supply: regulator support for MPP LED
- qcom,mpp-power-max-voltage - maximum voltage for MPP LED regulator. This should not be specified when no regulator is in use.
- qcom,mpp-power-min-voltage - minimum voltage for MPP LED regulator. This should not be specified when no regulator is in use.
Required properties for PWM mode only:
- pwms: Use the phandle of pwm device
- qcom,pwm-us: time the pwm device will modulate at (us) 
验证led
查看是否red与green的节点是否建立成功
cd /sys/class/leds/
//请查看是否出现了red与green目录
//可以通过控制目录中的pwm_us与brightness节点进行验证
关led 0
echo 100 > /sys/class/leds/red/pwm_us
echo  0    > /sys/class/leds/red/brightness
echo 100 > /sys/class/leds/green/pwm_us
echo  0    > /sys/class/leds/green/brightness 
开红灯
echo 100 > /sys/class/leds/green/pwm_us
echo  0    > /sys/class/leds/green/brightness
echo 100 > /sys/class/leds/red/pwm_us
echo  255   > /sys/class/leds/red/brightness
开绿灯
echo 100 > /sys/class/leds/red/pwm_us
echo  0  > /sys/class/leds/red/brightness
echo 100 > /sys/class/leds/green/pwm_us
echo  255    > /sys/class/leds/green/brightness
  • 4
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 20
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值