#include #include #include #include #include #include "hui_internal.h" #include "hui_generic.h" #define NUM_CHAN 3 #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0) struct pwm_state { unsigned int period; unsigned int duty_cycle; bool enabled; }; static inline int pwm_set_relative_duty_cycle(struct pwm_state *state, unsigned int duty_cycle, unsigned int scale) { if (!scale || duty_cycle > scale) return -EINVAL; state->duty_cycle = DIV_ROUND_CLOSEST_ULL((u64)duty_cycle * state->period, scale); return 0; } static inline void pwm_init_state(const struct pwm_device *pwm, struct pwm_state *state) { state->period = pwm_get_period(pwm); state->enabled = pwm_is_enabled(pwm); state->duty_cycle = 0; } static inline int pwm_apply_state(struct pwm_device *pwm, const struct pwm_state *state) { if (!state->enabled && pwm_is_enabled(pwm)) pwm_disable(pwm); pwm_config(pwm, state->duty_cycle, state->period); if (state->enabled && !pwm_is_enabled(pwm)) pwm_enable(pwm); return 0; } #endif struct hui_generic_led_pwm { struct hui_generic_led led; struct pwm_device *pwms[NUM_CHAN]; }; static int pwm_led_out(struct hui_generic_led *_led, struct led_color color) { int j; struct hui_generic_led_pwm *led = container_of(_led, struct hui_generic_led_pwm, led); color = led_color_premultiply(color); for (j = 0; j < NUM_CHAN; j++) { u32 c = color.c[j]; struct pwm_state state; if (!led->pwms[j]) continue; pwm_init_state(led->pwms[j], &state); pwm_set_relative_duty_cycle(&state, c, 255); state.enabled = true; pwm_apply_state(led->pwms[j], &state); } return 0; } static int led_probe(struct platform_device *pdev) { int err, count, i; struct device *dev = &pdev->dev; struct hui_generic_led_pwm *led; struct device_node *node = dev->of_node; const char *name = node->name; const char *type; led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); if (!led) return -ENOMEM; led->led.dev = dev; led->led.name = name; led->led.code = BLINK_CODE(.type = blink_off); led->led.out = pwm_led_out; err = of_property_read_string(node, "led-type", &type); if (err == -EINVAL) type = name; else if (err) return err; count = of_property_count_strings(node, "pwm-names"); if (count > 3) { dev_err(dev, "Ignoring led: more than 3 pwms\n"); return -EINVAL; } // Only supports single pwm if (count < 0) { led->pwms[0] = devm_pwm_get(dev, NULL); if (IS_ERR(led->pwms[0])) return PTR_ERR(led->pwms[0]); } else { for (i = 0; i < count; i++) { const char *con_id; err = of_property_read_string_index(node, "pwm-names", i, &con_id); if (err < 0) return err; led->pwms[i] = devm_pwm_get(dev, con_id); if (IS_ERR(led->pwms[i])) return PTR_ERR(led->pwms[i]); } } led->led.handle = avm_hui_add_led(name, type, node, &hui_generic_ops, &led->led); // TODO add as res to free later if (IS_ERR(led->led.handle)) { return PTR_ERR(led->led.handle); } err = hui_generic_blink_add_led(&led->led); if (err) return err; return 0; } static const struct of_device_id hui_generic_pwm_led_match[] = { { .compatible = "avm,hui-generic-led-pwm" }, {}, }; MODULE_DEVICE_TABLE(of, hui_generic_pwm_led_match); struct platform_driver hui_generic_led_pwm = { .probe = led_probe, .driver = { .name = "avm,hui-generic-led-pwm", .of_match_table = hui_generic_pwm_led_match, }, };