Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7eca9e35 authored by qctecmdr's avatar qctecmdr Committed by Gerrit - the friendly Code Review server
Browse files

Merge "leds: leds-pwm: Add support for blink_set callback"

parents c08a2c91 d90d34bc
Loading
Loading
Loading
Loading
+136 −13
Original line number Diff line number Diff line
@@ -22,12 +22,29 @@
#include <linux/leds_pwm.h>
#include <linux/slab.h>

#define PWM_PERIOD_DEFAULT_NS 1000000

struct pwm_setting {
	u64	period_ns;
	u64	duty_ns;
};

struct led_setting {
	u64			on_ms;
	u64			off_ms;
	enum led_brightness	brightness;
	bool			blink;
};

struct led_pwm_data {
	struct led_classdev	cdev;
	struct pwm_device	*pwm;
	struct pwm_setting	pwm_setting;
	struct led_setting	led_setting;
	unsigned int		active_low;
	unsigned int		period;
	int			duty;
	bool			blinking;
};

struct led_pwm_priv {
@@ -35,35 +52,139 @@ struct led_pwm_priv {
	struct led_pwm_data leds[0];
};

static void __led_pwm_set(struct led_pwm_data *led_dat)
static int __led_blink_config_pwm(struct led_pwm_data *led_data)
{
	struct pwm_state pstate;
	int rc;

	pwm_get_state(led_data->pwm, &pstate);
	pstate.enabled = !!(led_data->pwm_setting.duty_ns != 0);
	pstate.period = led_data->pwm_setting.period_ns;
	pstate.duty_cycle = led_data->pwm_setting.duty_ns;
	/* Use default pattern in PWM device */
	pstate.output_pattern = NULL;

	rc = pwm_apply_state(led_data->pwm, &pstate);
	if (rc < 0)
		pr_err("Apply PWM state for %s led failed, rc=%d\n",
				led_data->cdev.name, rc);

	return rc;
}

static int led_pwm_set_blink(struct led_pwm_data *led_data)
{
	u64 on_ms, off_ms, period_ns, duty_ns;
	enum led_brightness brightness = led_data->led_setting.brightness;
	int rc = 0;

	if (led_data->led_setting.blink) {
		on_ms = led_data->led_setting.on_ms;
		off_ms = led_data->led_setting.off_ms;

		duty_ns = on_ms * NSEC_PER_MSEC;
		period_ns = (on_ms + off_ms) * NSEC_PER_MSEC;

		if (period_ns < duty_ns && duty_ns != 0)
			period_ns = duty_ns + 1;

	} else {
		/* Use initial period if no blinking is required */
		period_ns = PWM_PERIOD_DEFAULT_NS;

		duty_ns = period_ns * brightness;
		do_div(duty_ns, LED_FULL);

		if (period_ns < duty_ns && duty_ns != 0)
			period_ns = duty_ns + 1;
	}

	pr_debug("BLINK: PWM settings for %s led: period = %lluns, duty = %lluns brightness = %d\n",
			led_data->cdev.name, period_ns, duty_ns, brightness);

	led_data->pwm_setting.duty_ns = duty_ns;
	led_data->pwm_setting.period_ns = period_ns;

	rc = __led_blink_config_pwm(led_data);
	if (rc < 0) {
		pr_err("failed to config pwm for blink %s failed, rc=%d\n",
				led_data->cdev.name, rc);
		return rc;
	}

	if (led_data->led_setting.blink) {
		led_data->cdev.brightness = LED_FULL;
		led_data->blinking = true;
	} else {
		led_data->cdev.brightness = led_data->led_setting.brightness;
		led_data->blinking = false;
	}

	return rc;
}

static int led_pwm_blink_set(struct led_classdev *led_cdev,
	unsigned long *on_ms, unsigned long *off_ms)
{
	struct led_pwm_data *led_data =
		container_of(led_cdev, struct led_pwm_data, cdev);
	int rc = 0;

	if (led_data->blinking && *on_ms == led_data->led_setting.on_ms &&
				*off_ms == led_data->led_setting.off_ms) {
		pr_debug("Ignore, on/off setting is not changed: on %lums, off %lums\n",
			*on_ms, *off_ms);
		return 0;
	}

	if (*on_ms == 0) {
		led_data->led_setting.blink = false;
		led_data->led_setting.brightness = LED_OFF;
	} else if (*off_ms == 0) {
		led_data->led_setting.blink = false;
		led_data->led_setting.brightness = led_data->cdev.brightness;
	} else {
		led_data->led_setting.on_ms = *on_ms;
		led_data->led_setting.off_ms = *off_ms;
		led_data->led_setting.blink = true;
	}

	rc = led_pwm_set_blink(led_data);
	if (rc < 0)
		pr_err("blink led failed for rc=%d\n", rc);

	return rc;
}

static void __led_pwm_set(struct led_pwm_data *led_data)
{
	int new_duty = led_dat->duty;
	int new_duty = led_data->duty;

	pwm_config(led_dat->pwm, new_duty, led_dat->period);
	pwm_config(led_data->pwm, new_duty, led_data->period);

	if (new_duty == 0)
		pwm_disable(led_dat->pwm);
		pwm_disable(led_data->pwm);
	else
		pwm_enable(led_dat->pwm);
		pwm_enable(led_data->pwm);
}

static int led_pwm_set(struct led_classdev *led_cdev,
		       enum led_brightness brightness)
{
	struct led_pwm_data *led_dat =
	struct led_pwm_data *led_data =
		container_of(led_cdev, struct led_pwm_data, cdev);
	unsigned int max = led_dat->cdev.max_brightness;
	unsigned long long duty =  led_dat->period;
	unsigned int max = led_data->cdev.max_brightness;
	unsigned long long duty =  led_data->period;

	duty *= brightness;
	do_div(duty, max);

	if (led_dat->active_low)
		duty = led_dat->period - duty;
	if (led_data->active_low)
		duty = led_data->period - duty;

	led_dat->duty = duty;
	led_data->duty = duty;

	__led_pwm_set(led_dat);
	__led_pwm_set(led_data);

	return 0;
}
@@ -92,7 +213,8 @@ static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv,
	led_data->cdev.default_trigger = led->default_trigger;
	led_data->cdev.brightness = LED_OFF;
	led_data->cdev.max_brightness = led->max_brightness;
	led_data->cdev.flags = LED_CORE_SUSPENDRESUME;
	/* Set a flag to keep the trigger always */
	led_data->cdev.flags |= LED_KEEP_TRIGGER;

	if (child)
		led_data->pwm = devm_of_pwm_get(dev, child, NULL);
@@ -107,6 +229,7 @@ static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv,
	}

	led_data->cdev.brightness_set_blocking = led_pwm_set;
	led_data->cdev.blink_set = led_pwm_blink_set;

	/*
	 * FIXME: pwm_apply_args() should be removed when switching to the