/* * pwm backlight support for barebox * * (C) Copyright 2014 Sascha Hauer, Pengutronix * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include <common.h> #include <malloc.h> #include <init.h> #include <video/backlight.h> #include <pwm.h> #include <linux/err.h> #include <of.h> #include <regulator.h> #include <gpio.h> #include <of_gpio.h> #include <asm-generic/div64.h> struct pwm_backlight { struct backlight_device backlight; struct pwm_device *pwm; struct regulator *power; uint32_t period; unsigned int *levels; int enable_gpio; int enable_active_high; int enabled; unsigned int scale; }; static int backlight_pwm_enable(struct pwm_backlight *pwm_backlight) { int ret; if (pwm_backlight->enabled) return 0; ret = pwm_enable(pwm_backlight->pwm); if (ret) return ret; regulator_enable(pwm_backlight->power); if (gpio_is_valid(pwm_backlight->enable_gpio)) { gpio_direction_output(pwm_backlight->enable_gpio, pwm_backlight->enable_active_high); } pwm_backlight->enabled = 1; return 0; } static int backlight_pwm_disable(struct pwm_backlight *pwm_backlight) { if (!pwm_backlight->enabled) return 0; if (gpio_is_valid(pwm_backlight->enable_gpio)) { gpio_direction_output(pwm_backlight->enable_gpio, !pwm_backlight->enable_active_high); regulator_disable(pwm_backlight->power); /* * Only disable PWM when an enable gpio is present. * The output of the PWM is undefined when the PWM * is disabled. */ pwm_disable(pwm_backlight->pwm); pwm_backlight->enabled = 0; } return 0; } static int compute_duty_cycle(struct pwm_backlight *pwm_backlight, int brightness) { int duty_cycle; if (pwm_backlight->levels) duty_cycle = pwm_backlight->levels[brightness]; else duty_cycle = brightness; return duty_cycle * pwm_backlight->period / pwm_backlight->scale; } static int backlight_pwm_set(struct backlight_device *backlight, int brightness) { struct pwm_backlight *pwm_backlight = container_of(backlight, struct pwm_backlight, backlight); pwm_config(pwm_backlight->pwm, compute_duty_cycle(pwm_backlight, brightness), pwm_backlight->period); if (brightness) return backlight_pwm_enable(pwm_backlight); else return backlight_pwm_disable(pwm_backlight); } static int pwm_backlight_parse_dt(struct device_d *dev, struct pwm_backlight *pwm_backlight) { struct device_node *node = dev->device_node; struct property *prop; int length; u32 value; int ret, i; enum of_gpio_flags flags; if (!node) return -ENODEV; ret = of_property_read_u32(node, "default-brightness-level", &value); if (ret < 0) return ret; pwm_backlight->backlight.brightness_default = value; /* determine the number of brightness levels */ prop = of_find_property(node, "brightness-levels", &length); if (!prop) return -EINVAL; length /= sizeof(u32); /* read brightness levels from DT property */ if (length > 0) { size_t size = sizeof(*pwm_backlight->levels) * length; pwm_backlight->levels = xzalloc(size); ret = of_property_read_u32_array(node, "brightness-levels", pwm_backlight->levels, length); if (ret < 0) return ret; pwm_backlight->backlight.brightness_max = length - 1; for (i = 0; i < length; i++) if (pwm_backlight->levels[i] > pwm_backlight->scale) pwm_backlight->scale = pwm_backlight->levels[i]; if (pwm_backlight->scale == 0) return -EINVAL; } else { /* We implicitly assume here a linear levels array { 0, 1, 2, ... 100 } */ pwm_backlight->scale = 100; pwm_backlight->backlight.brightness_max = pwm_backlight->scale; } pwm_backlight->enable_gpio = of_get_named_gpio_flags(node, "enable-gpios", 0, &flags); if (gpio_is_valid(pwm_backlight->enable_gpio)) { if (!(flags & OF_GPIO_ACTIVE_LOW)) pwm_backlight->enable_active_high = 1; } return 0; } static int backlight_pwm_of_probe(struct device_d *dev) { int ret; struct pwm_backlight *pwm_backlight; struct pwm_device *pwm; pwm = of_pwm_request(dev->device_node, NULL); if (IS_ERR(pwm)) { dev_err(dev, "Cannot find PWM device\n"); return PTR_ERR(pwm); } pwm_backlight = xzalloc(sizeof(*pwm_backlight)); pwm_backlight->pwm = pwm; pwm_backlight->period = pwm_get_period(pwm); ret = pwm_backlight_parse_dt(dev, pwm_backlight); if (ret) return ret; pwm_backlight->power = regulator_get(dev, "power"); if (IS_ERR(pwm_backlight->power)) { dev_err(dev, "Cannot find regulator\n"); return PTR_ERR(pwm_backlight->power); } pwm_backlight->backlight.slew_time_ms = 100; pwm_backlight->backlight.brightness_set = backlight_pwm_set; pwm_backlight->backlight.dev.parent = dev; pwm_backlight->backlight.node = dev->device_node; ret = backlight_register(&pwm_backlight->backlight); if (ret) return ret; return 0; } static struct of_device_id backlight_pwm_of_ids[] = { { .compatible = "pwm-backlight", }, { /* sentinel */ } }; static struct driver_d backlight_pwm_of_driver = { .name = "pwm-backlight", .probe = backlight_pwm_of_probe, .of_compatible = DRV_OF_COMPAT(backlight_pwm_of_ids), }; device_platform_driver(backlight_pwm_of_driver);