text: /*
   * Copyright (c) 2020 Seagate Technology LLC
   *
   * SPDX-License-Identifier: Apache-2.0
   */
  
  #define DT_DRV_COMPAT pwm_leds
  
  /**
   * @file
   * @brief PWM driven LEDs
   */
  
  #include <drivers/led.h>
  #include <drivers/pwm.h>
  #include <device.h>
  #include <zephyr.h>
  #include <sys/math_extras.h>
  
  #include <logging/log.h>
  LOG_MODULE_REGISTER(led_pwm, CONFIG_LED_LOG_LEVEL);
  
  #define DEV_CFG(dev)	((const struct led_pwm_config *) ((dev)->config))
  
  struct led_pwm {
  	const struct device *dev;
  	uint32_t channel;
  	uint32_t period;
  	pwm_flags_t flags;
  };
  
  struct led_pwm_config {
  	int num_leds;
  	const struct led_pwm *led;
  };
  
  static int led_pwm_blink(const struct device *dev, uint32_t led,
  			 uint32_t delay_on, uint32_t delay_off)
  {
  	const struct led_pwm_config *config = DEV_CFG(dev);
  	const struct led_pwm *led_pwm;
  	uint32_t period_usec, pulse_usec;
  
  	if (led >= config->num_leds) {
  		return -EINVAL;
  	}
  
  	/*
  	 * Convert delays (in ms) into PWM period and pulse (in us) and check
  	 * for overflows.
  	 */
  	if (u32_add_overflow(delay_on, delay_off, &period_usec) ||
  	    u32_mul_overflow(period_usec, 1000, &period_usec) ||
  	    u32_mul_overflow(delay_on, 1000, &pulse_usec)) {
  		return -EINVAL;
  	}
  
  	led_pwm = &config->led[led];
  
  	return pwm_pin_set_usec(led_pwm->dev, led_pwm->channel,
  				period_usec, pulse_usec, led_pwm->flags);
  }
  
  static int led_pwm_set_brightness(const struct device *dev,
  				  uint32_t led, uint8_t value)
  {
  	const struct led_pwm_config *config = DEV_CFG(dev);
  	const struct led_pwm *led_pwm;
  	uint32_t pulse;
  
  	if (led >= config->num_leds || value > 100) {
  		return -EINVAL;
  	}
  
  	led_pwm = &config->led[led];
  
  	pulse = led_pwm->period * value / 100;
  
  	return pwm_pin_set_cycles(led_pwm->dev, led_pwm->channel,
  				  led_pwm->period, pulse, led_pwm->flags);
  }
  
  static int led_pwm_on(const struct device *dev, uint32_t led)
  {
  	return led_pwm_set_brightness(dev, led, 100);
  }
  
  static int led_pwm_off(const struct device *dev, uint32_t led)
  {
  	return led_pwm_set_brightness(dev, led, 0);
  }
  
  static int led_pwm_init(const struct device *dev)
  {
  	const struct led_pwm_config *config = DEV_CFG(dev);
  	int i;
  
  	if (!config->num_leds) {
  		LOG_ERR("%s: no LEDs found (DT child nodes missing)",
  			dev->name);
  		return -ENODEV;
  	}
  
  	for (i = 0; i < config->num_leds; i++) {
  		const struct led_pwm *led = &config->led[i];
  
  		if (!device_is_ready(led->dev)) {
  			LOG_ERR("%s: pwm device not ready", dev->name);
  			return -ENODEV;
  		}
  	}
  
  	return 0;
  }
  
  #ifdef CONFIG_PM_DEVICE
  static int led_pwm_pm_action(const struct device *dev,
  			     enum pm_device_action action)
  {
  	const struct led_pwm_config *config = DEV_CFG(dev);
  
  	/* switch all underlying PWM devices to the new state */
  	for (size_t i = 0; i < config->num_leds; i++) {
  		int err;
  		enum pm_device_state state;
  		const struct led_pwm *led_pwm = &config->led[i];
  
  		LOG_DBG("Switching PWM %p to state %" PRIu32, led_pwm->dev, state);
  
  		/* NOTE: temporary solution, deserves proper fix */
  		switch (action) {
  		case PM_DEVICE_ACTION_RESUME:
  			state = PM_DEVICE_STATE_ACTIVE;
  			break;
  		case PM_DEVICE_ACTION_SUSPEND:
  			state = PM_DEVICE_STATE_SUSPENDED;
  			break;
  		default:
  			return -ENOTSUP;
  		}
  
  		err = pm_device_state_set(led_pwm->dev, state);
  		if (err && (err != -EALREADY)) {
  			LOG_ERR("Cannot switch PWM %p power state", led_pwm->dev);
  		}
  	}
  
  	return 0;
  }
  #endif /* CONFIG_PM_DEVICE */
  
  static const struct led_driver_api led_pwm_api = {
  	.on		= led_pwm_on,
  	.off		= led_pwm_off,
  	.blink		= led_pwm_blink,
  	.set_brightness	= led_pwm_set_brightness,
  };
  
  #define LED_PWM(led_node_id)						\
  {									\
  	.dev		= DEVICE_DT_GET(DT_PWMS_CTLR(led_node_id)),	\
  	.channel	= DT_PWMS_CHANNEL(led_node_id),			\
  	.period		= DT_PHA_OR(led_node_id, pwms, period, 100),	\
  	.flags		= DT_PHA_OR(led_node_id, pwms, flags,		\
  				    PWM_POLARITY_NORMAL),		\
  },
  
  #define LED_PWM_DEVICE(id)					\
  								\
  static const struct led_pwm led_pwm_##id[] = {			\
  	DT_INST_FOREACH_CHILD(id, LED_PWM)			\
  };								\
  								\
  static const struct led_pwm_config led_pwm_config_##id = {	\
  	.num_leds	= ARRAY_SIZE(led_pwm_##id),		\
  	.led		= led_pwm_##id,				\
  };								\
  								\
  DEVICE_DT_INST_DEFINE(id, &led_pwm_init, led_pwm_pm_action,	\
  		      NULL, &led_pwm_config_##id, POST_KERNEL,	\
  		      CONFIG_LED_INIT_PRIORITY, &led_pwm_api);
  
  DT_INST_FOREACH_STATUS_OKAY(LED_PWM_DEVICE)
  