Orange Pi5 kernel

Deprecated Linux kernel 5.10.110 for OrangePi 5/5B/5+ boards

3 Commits   0 Branches   0 Tags
/*
 * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
 * Author:Mark Yao <mark.yao@rock-chips.com>
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * 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 <drm/drm.h>
#include <drm/drmP.h>
#include <drm/drm_panel.h>

#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/pwm_backlight.h>
#include <uapi/drm/rockchip_drm.h>

#include "rockchip_drm_drv.h"
#include "rockchip_drm_backlight.h"

struct sub_backlight {
	struct pwm_device *pwm;
	struct pinctrl *pinctrl;
	struct pinctrl_state *dummy_state;
	struct pinctrl_state *active_state;
	struct drm_crtc *crtc;
	struct device *dev;

	const struct rockchip_sub_backlight_ops *ops;
	struct list_head list;
};

#define to_rockchip_backlight_device(x) \
		container_of((x), struct rockchip_drm_backlight, pwm_pdev)

static LIST_HEAD(backlight_list);

static int compute_duty_cycle(struct rockchip_drm_backlight *bl,
			      int brightness, int period)
{
	unsigned int lth = bl->lth_brightness;
	int duty_cycle;

	duty_cycle = bl->levels[brightness];

	return (duty_cycle * (period - lth) / bl->scale) + lth;
}

static void rockchip_pwm_power_on(struct rockchip_drm_backlight *bl,
				  struct pwm_device *pwm, int brightness)
{
	struct pwm_args pargs;
	int duty_cycle;

	pwm_get_args(pwm, &pargs);
	duty_cycle = compute_duty_cycle(bl, brightness, pargs.period);
	pwm_config(pwm, duty_cycle, pargs.period);
	pwm_enable(pwm);
}

static void rockchip_pwm_power_off(struct rockchip_drm_backlight *bl,
				   struct pwm_device *pwm)
{
	struct pwm_args pargs;
	struct pwm_state state;

	pwm_get_state(pwm, &state);
	if (!state.enabled)
		return;

	pwm_get_args(pwm, &pargs);
	pwm_config(pwm, 0, pargs.period);
	pwm_disable(pwm);
}

static void rockchip_backlight_power_on(struct rockchip_drm_backlight *bl)
{
	int err;

	if (bl->enabled)
		return;

	err = regulator_enable(bl->power_supply);
	if (err < 0)
		dev_err(bl->dev, "failed to enable power supply\n");

	if (bl->enable_gpio)
		gpiod_set_value(bl->enable_gpio, 1);

	bl->enabled = true;
}

static void rockchip_backlight_power_off(struct rockchip_drm_backlight *bl)
{
	if (!bl->enabled)
		return;

	if (bl->enable_gpio)
		gpiod_set_value(bl->enable_gpio, 0);

	regulator_disable(bl->power_supply);
	bl->enabled = false;
}

static int backlight_parse_dt(struct rockchip_drm_backlight *bl)
{
	struct device_node *node = bl->dev->of_node;
	struct device *dev = bl->dev;
	struct property *prop;
	size_t size;
	int length;
	u32 value;
	int ret, i;

	if (!node)
		return -ENODEV;

	bl->pwm = devm_pwm_get(dev, NULL);
	if (IS_ERR(bl->pwm)) {
		dev_err(dev, "unable to request PWM: %ld\n",
			PTR_ERR(bl->pwm));
		return PTR_ERR(bl->pwm);
	}

	if (!bl->pwm->chip->dev->pins) {
		dev_err(dev, "failed to find pwm pinctrl\n");
		return -ENODEV;
	}
	bl->dummy_state = pinctrl_lookup_state(bl->pwm->chip->dev->pins->p,
					       "dummy");
	if (IS_ERR_OR_NULL(bl->dummy_state)) {
		dev_err(dev, "failed to find pwm dummy state\n");
		return -ENODEV;
	}

	bl->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_ASIS);
	if (IS_ERR(bl->enable_gpio)) {
		dev_err(dev, "unable to request enable gpio: %ld\n",
			PTR_ERR(bl->enable_gpio));
		return PTR_ERR(bl->enable_gpio);
	}

	bl->power_supply = devm_regulator_get(dev, "power");
	if (IS_ERR(bl->power_supply)) {
		dev_err(dev, "unable to request power supply: %ld\n",
			PTR_ERR(bl->power_supply));
		return PTR_ERR(bl->power_supply);
	}

	/* determine the number of brightness levels */
	prop = of_find_property(node, "brightness-levels", &length);
	if (!prop)
		return -EINVAL;

	bl->max_brightness = length / sizeof(u32);

	if (bl->max_brightness <= 0)
		return -EINVAL;

	/* read brightness levels from DT property */
	size = sizeof(*bl->levels) * bl->max_brightness;

	bl->levels = devm_kzalloc(dev, size, GFP_KERNEL);
	if (!bl->levels)
		return -ENOMEM;

	ret = of_property_read_u32_array(node, "brightness-levels",
					 bl->levels,
					 bl->max_brightness);
	if (ret < 0)
		return ret;

	ret = of_property_read_u32(node, "default-brightness-level", &value);
	if (ret < 0)
		return ret;

	bl->dft_brightness = value;
	bl->max_brightness--;

	for (i = 0; i <= bl->max_brightness; i++)
		if (bl->levels[i] > bl->scale)
			bl->scale = bl->levels[i];

	return 0;
}

void rockchip_drm_backlight_update(struct drm_device *drm)
{
	struct rockchip_drm_private *private = drm->dev_private;
	struct rockchip_drm_backlight *bl = private->backlight;
	struct drm_connector *connector;
	struct sub_backlight *sub;
	struct rockchip_crtc_state *s;
	struct drm_crtc *crtc;
	bool backlight_changed = false;

	if (!bl || !bl->connector)
		return;

	sub = bl->sub;
	connector = bl->connector;
	crtc = connector->state->crtc;
	if (!crtc) {
		if (sub) {
			bl->sub = NULL;
			backlight_changed = true;
		}
	} else if (!sub || sub->dev->of_node != crtc->port) {
		s = to_rockchip_crtc_state(crtc->state);
		if (s->cabc_mode != ROCKCHIP_DRM_CABC_MODE_DISABLE) {
			list_for_each_entry(sub, &backlight_list, list) {
				if (sub->crtc == crtc) {
					bl->sub = sub;
					backlight_changed = true;
					break;
				}
			}
		} else if (bl->sub) {
			bl->sub = NULL;
			backlight_changed = true;
		}
	}

	if (backlight_changed)
		backlight_update_status(bl->bd);
}
EXPORT_SYMBOL(rockchip_drm_backlight_update);

int of_rockchip_drm_sub_backlight_register(struct device *dev,
				struct drm_crtc *crtc,
				const struct rockchip_sub_backlight_ops *ops)
{
	struct sub_backlight *sub;
	struct pwm_device *pwm;

	pwm = devm_pwm_get(dev, NULL);
	if (IS_ERR(pwm)) {
		dev_dbg(dev, "unable to request PWM\n");
		return PTR_ERR(pwm);
	}

	sub = devm_kzalloc(dev, sizeof(*sub), GFP_KERNEL);
	if (!sub)
		return -ENOMEM;

	sub->pinctrl = devm_pinctrl_get(pwm->chip->dev);
	if (IS_ERR(sub->pinctrl)) {
		dev_err(dev, "failed to find pwm pinctrl\n");
		return PTR_ERR(sub->pinctrl);
	}

	sub->dummy_state = pinctrl_lookup_state(sub->pinctrl, "dummy");
	if (IS_ERR_OR_NULL(sub->dummy_state)) {
		dev_err(dev, "failed to find pwm dummy state\n");
		return -ENODEV;
	}

	sub->active_state = pinctrl_lookup_state(sub->pinctrl, "active");
	if (IS_ERR_OR_NULL(sub->active_state)) {
		dev_err(dev, "failed to find pwm active state\n");
		return -ENODEV;
	}

	pwm_adjust_config(pwm);

	sub->pwm = pwm;
	sub->dev = dev;
	sub->crtc = crtc;
	sub->ops = ops;

	INIT_LIST_HEAD(&sub->list);
	list_add_tail(&sub->list, &backlight_list);

	return 0;
}
EXPORT_SYMBOL(of_rockchip_drm_sub_backlight_register);

static int rockchip_drm_backlight_bind(struct device *dev,
				       struct device *master, void *data)
{
	struct drm_device *drm_dev = data;
	struct rockchip_drm_private *private = drm_dev->dev_private;
	struct rockchip_drm_backlight *bl = dev_get_drvdata(dev);
	struct drm_connector *connector;
	struct drm_panel *panel;
	struct device_node *backlight_np;

	private->backlight = bl;

	mutex_lock(&drm_dev->mode_config.mutex);

	drm_for_each_connector(connector, drm_dev) {
		panel = drm_find_panel_by_connector(connector);
		if (!panel || !panel->dev)
			continue;
		backlight_np = of_parse_phandle(panel->dev->of_node,
						"backlight", 0);
		if (backlight_np == dev->of_node) {
			bl->connector = connector;
			break;
		}
	}

	mutex_unlock(&drm_dev->mode_config.mutex);

	if (!bl->connector) {
		dev_warn(dev, "failed to bind drm backlight\n");
		return -ENODEV;
	}

	return 0;
}

static void rockchip_drm_backlight_unbind(struct device *dev,
					  struct device *master, void *data)
{
	struct drm_device *drm_dev = data;
	struct rockchip_drm_private *private = drm_dev->dev_private;
	struct rockchip_drm_backlight *bl = dev_get_drvdata(dev);

	private->backlight = NULL;
	bl->connector = NULL;
}

static const struct component_ops rockchip_drm_backlight_component_ops = {
	.bind = rockchip_drm_backlight_bind,
	.unbind = rockchip_drm_backlight_unbind,
};

static int
rockchip_drm_backlight_update_status(struct backlight_device *backlight)
{
	int brightness = backlight->props.brightness;
	struct rockchip_drm_backlight *bl = bl_get_data(backlight);
	struct sub_backlight *sub = bl->sub;
	struct sub_backlight *current_sub = bl->current_sub;
	bool async;

	if (backlight->props.power != FB_BLANK_UNBLANK ||
	    backlight->props.fb_blank != FB_BLANK_UNBLANK ||
	    backlight->props.state & BL_CORE_FBBLANK)
		brightness = 0;

	if (!sub || brightness <= 0) {
		if (current_sub)
			pinctrl_select_state(current_sub->pinctrl,
					     current_sub->dummy_state);

		if (brightness > 0) {
			rockchip_pwm_power_on(bl, bl->pwm, brightness);
			rockchip_backlight_power_on(bl);
		} else {
			rockchip_backlight_power_off(bl);
			rockchip_pwm_power_off(bl, bl->pwm);
		}

		pinctrl_pm_select_default_state(bl->pwm->chip->dev);
		if (current_sub) {
			rockchip_pwm_power_off(bl, current_sub->pwm);

			if (current_sub->ops->config_done)
				current_sub->ops->config_done(current_sub->dev,
							      true);
		}

		return 0;
	}

	pinctrl_select_state(bl->pwm->chip->dev->pins->p, bl->dummy_state);

	async = !!current_sub;
	if (current_sub && sub != current_sub) {
		pinctrl_select_state(current_sub->pinctrl,
				     current_sub->dummy_state);
		async = false;
	}

	rockchip_pwm_power_on(bl, sub->pwm, brightness);

	if (current_sub && sub != current_sub) {
		rockchip_pwm_power_on(bl, current_sub->pwm, brightness);
		if (current_sub->ops->config_done)
			current_sub->ops->config_done(current_sub->dev, true);
	}

	if (sub->ops->config_done)
		sub->ops->config_done(sub->dev, async);

	pinctrl_select_state(sub->pinctrl, sub->active_state);

	rockchip_backlight_power_on(bl);

	bl->current_sub = sub;

	return 0;
}

static const struct backlight_ops rockchip_drm_backlight_ops = {
	.update_status = rockchip_drm_backlight_update_status,
};

static int rockchip_drm_backlight_probe(struct platform_device *pdev)
{
	struct rockchip_drm_backlight *bl;
	struct backlight_properties props;
	struct device *dev = &pdev->dev;
	struct device_node *node = dev->of_node;
	int initial_blank = FB_BLANK_UNBLANK;
	int ret;

	bl = devm_kzalloc(dev, sizeof(*bl), GFP_KERNEL);
	if (!bl)
		return -ENOMEM;

	bl->dev = dev;
	ret = backlight_parse_dt(bl);
	if (ret < 0) {
		dev_err(&pdev->dev, "failed to find parse backlight dts\n");
		return ret;
	}

	bl->enabled = false;

	if (bl->enable_gpio) {
		/*
		 * If the driver is probed from the device tree and there is a
		 * phandle link pointing to the backlight node, it is safe to
		 * assume that another driver will enable the backlight at the
		 * appropriate time. Therefore, if it is disabled, keep it so.
		 */
		if (node && node->phandle &&
		    gpiod_get_direction(bl->enable_gpio) == GPIOF_DIR_OUT &&
		    gpiod_get_value(bl->enable_gpio) == 0)
			initial_blank = FB_BLANK_POWERDOWN;
		else
			gpiod_direction_output(bl->enable_gpio, 1);
	}

	if (node && node->phandle && !regulator_is_enabled(bl->power_supply))
		initial_blank = FB_BLANK_POWERDOWN;

	pwm_adjust_config(bl->pwm);

	memset(&props, 0, sizeof(props));
	props.type = BACKLIGHT_RAW;
	props.max_brightness = bl->max_brightness;
	bl->bd = backlight_device_register(dev_name(dev), dev, bl,
					   &rockchip_drm_backlight_ops, &props);
	if (IS_ERR(bl->bd)) {
		dev_err(&pdev->dev, "failed to register backlight\n");
		ret = PTR_ERR(bl->bd);
		return ret;
	}

	bl->bd->props.brightness = bl->dft_brightness;
	bl->bd->props.power = initial_blank;
	backlight_update_status(bl->bd);

	platform_set_drvdata(pdev, bl);

	ret = component_add(dev, &rockchip_drm_backlight_component_ops);
	if (ret)
		backlight_device_unregister(bl->bd);

	return ret;
}

static int rockchip_drm_backlight_remove(struct platform_device *pdev)
{
	struct rockchip_drm_backlight *bl = platform_get_drvdata(pdev);

	backlight_device_unregister(bl->bd);
	component_del(&pdev->dev, &rockchip_drm_backlight_component_ops);

	return 0;
}

static const struct of_device_id rockchip_drm_backlight_dt_ids[] = {
	{.compatible = "rockchip,drm-backlight",
	 .data = NULL },
	{}
};
MODULE_DEVICE_TABLE(of, rockchip_drm_backlight_dt_ids);

static struct platform_driver rockchip_drm_backlight_driver = {
	.probe = rockchip_drm_backlight_probe,
	.remove = rockchip_drm_backlight_remove,
	.driver = {
		   .name = "drm-backlight",
		   .of_match_table =
			of_match_ptr(rockchip_drm_backlight_dt_ids),
	},
};

module_platform_driver(rockchip_drm_backlight_driver);

MODULE_AUTHOR("Mark Yao <mark.yao@rock-chips.com>");
MODULE_DESCRIPTION("Rockchip Drm Backlight Driver");
MODULE_LICENSE("GPL v2");