// SPDX-License-Identifier: GPL-2.0
// LED Multicolor class interface
// Copyright (C) 2019-20 Texas Instruments Incorporated - http://www.ti.com/
// Author: Dan Murphy <dmurphy@ti.com>

#include <linux/device.h>
#include <linux/init.h>
#include <linux/led-class-multicolor.h>
#include <linux/math.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#include "leds.h"

int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev,
				 enum led_brightness brightness)
{
	struct led_classdev *led_cdev = &mcled_cdev->led_cdev;
	int i;

	for (i = 0; i < mcled_cdev->num_colors; i++)
		mcled_cdev->subled_info[i].brightness =
			DIV_ROUND_CLOSEST(brightness *
					  mcled_cdev->subled_info[i].intensity,
					  led_cdev->max_brightness);

	return 0;
}
EXPORT_SYMBOL_GPL(led_mc_calc_color_components);

static ssize_t multi_intensity_store(struct device *dev,
				struct device_attribute *intensity_attr,
				const char *buf, size_t size)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
	int nrchars, offset = 0;
	int intensity_value[LED_COLOR_ID_MAX];
	int i;
	ssize_t ret;

	mutex_lock(&led_cdev->led_access);

	for (i = 0; i < mcled_cdev->num_colors; i++) {
		ret = sscanf(buf + offset, "%i%n",
			     &intensity_value[i], &nrchars);
		if (ret != 1) {
			ret = -EINVAL;
			goto err_out;
		}
		offset += nrchars;
	}

	offset++;
	if (offset < size) {
		ret = -EINVAL;
		goto err_out;
	}

	for (i = 0; i < mcled_cdev->num_colors; i++)
		mcled_cdev->subled_info[i].intensity = intensity_value[i];

	led_set_brightness(led_cdev, led_cdev->brightness);
	ret = size;
err_out:
	mutex_unlock(&led_cdev->led_access);
	return ret;
}

static ssize_t multi_intensity_show(struct device *dev,
			      struct device_attribute *intensity_attr,
			      char *buf)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
	int len = 0;
	int i;

	for (i = 0; i < mcled_cdev->num_colors; i++) {
		len += sprintf(buf + len, "%d",
			       mcled_cdev->subled_info[i].intensity);
		if (i < mcled_cdev->num_colors - 1)
			len += sprintf(buf + len, " ");
	}

	buf[len++] = '\n';
	return len;
}
static DEVICE_ATTR_RW(multi_intensity);

static ssize_t multi_index_show(struct device *dev,
			      struct device_attribute *multi_index_attr,
			      char *buf)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
	int len = 0;
	int index;
	int i;

	for (i = 0; i < mcled_cdev->num_colors; i++) {
		index = mcled_cdev->subled_info[i].color_index;
		len += sprintf(buf + len, "%s", led_get_color_name(index));
		if (i < mcled_cdev->num_colors - 1)
			len += sprintf(buf + len, " ");
	}

	buf[len++] = '\n';
	return len;
}
static DEVICE_ATTR_RO(multi_index);

static struct attribute *led_multicolor_attrs[] = {
	&dev_attr_multi_intensity.attr,
	&dev_attr_multi_index.attr,
	NULL,
};
ATTRIBUTE_GROUPS(led_multicolor);

int led_classdev_multicolor_register_ext(struct device *parent,
				     struct led_classdev_mc *mcled_cdev,
				     struct led_init_data *init_data)
{
	struct led_classdev *led_cdev;

	if (!mcled_cdev)
		return -EINVAL;

	if (mcled_cdev->num_colors <= 0)
		return -EINVAL;

	if (mcled_cdev->num_colors > LED_COLOR_ID_MAX)
		return -EINVAL;

	led_cdev = &mcled_cdev->led_cdev;
	led_cdev->flags |= LED_MULTI_COLOR;
	mcled_cdev->led_cdev.groups = led_multicolor_groups;

	return led_classdev_register_ext(parent, led_cdev, init_data);
}
EXPORT_SYMBOL_GPL(led_classdev_multicolor_register_ext);

void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev)
{
	if (!mcled_cdev)
		return;

	led_classdev_unregister(&mcled_cdev->led_cdev);
}
EXPORT_SYMBOL_GPL(led_classdev_multicolor_unregister);

static void devm_led_classdev_multicolor_release(struct device *dev, void *res)
{
	led_classdev_multicolor_unregister(*(struct led_classdev_mc **)res);
}

int devm_led_classdev_multicolor_register_ext(struct device *parent,
					     struct led_classdev_mc *mcled_cdev,
					     struct led_init_data *init_data)
{
	struct led_classdev_mc **dr;
	int ret;

	dr = devres_alloc(devm_led_classdev_multicolor_release,
			  sizeof(*dr), GFP_KERNEL);
	if (!dr)
		return -ENOMEM;

	ret = led_classdev_multicolor_register_ext(parent, mcled_cdev,
						   init_data);
	if (ret) {
		devres_free(dr);
		return ret;
	}

	*dr = mcled_cdev;
	devres_add(parent, dr);

	return 0;
}
EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_register_ext);

static int devm_led_classdev_multicolor_match(struct device *dev,
					      void *res, void *data)
{
	struct led_classdev_mc **p = res;

	if (WARN_ON(!p || !*p))
		return 0;

	return *p == data;
}

void devm_led_classdev_multicolor_unregister(struct device *dev,
					     struct led_classdev_mc *mcled_cdev)
{
	WARN_ON(devres_release(dev,
			       devm_led_classdev_multicolor_release,
			       devm_led_classdev_multicolor_match, mcled_cdev));
}
EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_unregister);

MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
MODULE_DESCRIPTION("Multicolor LED class interface");
MODULE_LICENSE("GPL v2");
