// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */

#include <linux/module.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/device.h>
#include <linux/atomic.h>
#include <linux/amlogic/irblaster.h>

void irblaster_chip_data_clear(struct irblaster_chip *chip)
{
	chip->buffer = NULL;
	chip->buffer_len = 0;
	chip->sum_time = 0;
}

/**
 * irblaster_send() - send raw level data
 * @chip: irblaster controller
 * @data: raw level data (us)
 * @len: raw len
 */
int irblaster_send(struct irblaster_chip *chip, unsigned int *data,
		   unsigned int len)
{
	unsigned int sum_time = 0;
	int err, i;

	if (!chip || len == 0 || len > MAX_PLUSE) {
		pr_err("%s(): parameter error\n", __func__);
		return -EINVAL;
	}

	for (i = 0; i < len; i++)
		sum_time += data[i];

	chip->buffer = data;
	chip->buffer_len = len;
	chip->sum_time = sum_time;

	if (chip->ops->send) {
		err = chip->ops->send(chip, data, len);
		if (err)
			return err;
	} else {
		pr_err("%s(): irblaster func %s not found\n",
		       __func__, __func__);
		return -EINVAL;
	}

	irblaster_chip_data_clear(chip);

	return 0;
}

/**
 * irblaster_set_freq() - set irblaster freq
 * @chip: irblaster controller
 * @freq: irblaster freq (HZ)
 */
int irblaster_set_freq(struct irblaster_chip *chip, unsigned int freq)
{
	int ret;

	if (!chip || freq <= 0)
		return -EINVAL;

	if (chip->ops->set_freq) {
		ret = chip->ops->set_freq(chip, freq);
		if (ret)
			return -EINVAL;
	}

	chip->state.freq = freq;

	return 0;
}

/**
 * irblaster_get_freq() - get irblaster freq
 * @chip: irblaster controller
 */
unsigned int irblaster_get_freq(struct irblaster_chip *chip)
{
	unsigned int freq;

	if (!chip)
		return -EINVAL;

	if (chip->ops->get_freq) {
		freq = chip->ops->get_freq(chip);
		if (freq == 0)
			return -EINVAL;
	} else {
		freq = chip->state.freq;
	}

	return freq;
}

/**
 * irblaster_set_duty() - set irblaster duty
 * @chip: irblaster controller
 * @duty: irblaster duty
 */
int irblaster_set_duty(struct irblaster_chip *chip, unsigned int duty)
{
	int ret;

	if (!chip || duty <= 0 || duty > 100)
		return -EINVAL;

	if (chip->ops->set_duty) {
		ret = chip->ops->set_duty(chip, duty);
		if (ret)
			return -EINVAL;
	}

	chip->state.duty = duty;

	return 0;
}

/**
 * irblaster_set_duty() - set irblaster duty
 * @chip: irblaster controller
 * @duty: irblaster duty
 */
unsigned int irblaster_get_duty(struct irblaster_chip *chip)
{
	unsigned int duty;

	if (chip->ops->get_duty) {
		duty = chip->ops->get_duty(chip);
		if (duty == 0)
			return -EINVAL;
	} else {
		duty = chip->state.duty;
	}

	return duty;
}

/**
 * irblasterchip_remove() - remove a irblaster Controller
 * @chip: the irblaster chip to remove
 * @Returns: 0 on success or a negative error code on failure.
 */
int irblasterchip_remove(struct irblaster_chip *chip)
{
	mutex_lock(&irblaster_lock);
	list_del_init(&chip->list);

	if (chip->dev)
		of_node_put(chip->dev->of_node);

	irblasterchip_sysfs_unexport(chip);
	mutex_unlock(&irblaster_lock);

	return 0;
}
EXPORT_SYMBOL_GPL(irblasterchip_remove);

static bool irblaster_ops_check(struct irblaster_ops *ops)
{
	/* These one interfaces are the most basic of the irblaster */
	if (ops->send)
		return true;

	return false;
}

/**
 * irblasterchip_add() - register a new irblaster Controller
 * @chip: the irblaster chip to add
 * @Returns: 0 on success or a negative error code on failure.
 */
int irblasterchip_add(struct irblaster_chip *chip)
{
	if (!chip || !chip->dev || !chip->ops)
		return -EINVAL;

	if (!irblaster_ops_check(chip->ops))
		return -EINVAL;

	mutex_lock(&irblaster_lock);
	atomic_set(&chip->request, IRBLASTER_EXPORTED);
	INIT_LIST_HEAD(&chip->list);
	list_add(&chip->list, &irblaster_chips);
	irblasterchip_sysfs_export(chip);
	mutex_unlock(&irblaster_lock);

	return 0;
}
EXPORT_SYMBOL_GPL(irblasterchip_add);

/**
 * irblaster_put() - release a irblaster controller
 * @chip: irblaster controller
 */
void irblaster_put(struct irblaster_chip *chip)
{
	if (!chip)
		return;

	mutex_lock(&irblaster_lock);
	atomic_set(&chip->request, IRBLASTER_EXPORTED);
	irblaster_chip_data_clear(chip);
	irblasterchip_sysfs_export(chip);
	mutex_unlock(&irblaster_lock);
}
EXPORT_SYMBOL_GPL(irblaster_put);

static struct irblaster_chip *of_node_to_irblasterchip(struct device_node *np)
{
	struct irblaster_chip *chip;

	mutex_lock(&irblaster_lock);
	list_for_each_entry(chip, &irblaster_chips, list)
		if (chip->dev && chip->dev->of_node == np) {
			mutex_unlock(&irblaster_lock);
			if (atomic_read(&chip->request) == IRBLASTER_REQUESTED)
				return ERR_PTR(-EPROBE_DEFER);
			return chip;
		}

	mutex_unlock(&irblaster_lock);
	return ERR_PTR(-EPROBE_DEFER);
}

static int irblaster_set_default_state(struct irblaster_chip *pc,
				       const struct of_phandle_args *args)
{
	int ret;

	if (pc->of_irblaster_n_cells < 2 ||
	    args->args[0] <= 0 || args->args[1] > 100)
		return -EINVAL;

	pc->state.freq = args->args[0];
	pc->state.duty = args->args[1];

	ret = irblaster_set_freq(pc, pc->state.freq);
	if (ret)
		return -EINVAL;

	ret = irblaster_set_duty(pc, pc->state.duty);
	if (ret)
		return -EINVAL;

	return 0;
}

/**
 * of_irblaster_get() - request a irblaster via the irblaster framework
 * @np: device node to get the irblaster from
 * @con_id: consumer name
 *
 * Returns the irblaster controller parsed from the phandle and index
 * specified in the "irblaster-config" property of a device tree node
 * or a negative error-code on failure. Values parsed from the device
 * tree are stored in the returned irblaster device object.
 *
 * Returns: A pointer to the requested irblaster controller or an ERR_PTR()
 * -encoded error code on failure.
 */
struct irblaster_chip *of_irblaster_get(struct device_node *np,
					const char *con_id)
{
	struct of_phandle_args args;
	struct irblaster_chip *pc;
	int err, index = 0;

	err = of_parse_phandle_with_args(np, "irblaster-config",
					 "#irblaster-cells", index,
					 &args);
	if (err) {
		pr_err("%s(): can't parse \"irblaster-config\" property\n",
		       __func__);
		return ERR_PTR(err);
	}

	pc = of_node_to_irblasterchip(args.np);
	if (IS_ERR(pc)) {
		pr_err("%s(): irblaster chip not found\n", __func__);
		pc = ERR_PTR(-EINVAL);
		goto put;
	}

	if (args.args_count != pc->of_irblaster_n_cells) {
		pr_err("%s: wrong #irblaster-cells for %s\n", np->full_name,
		       args.np->full_name);
		pc = ERR_PTR(-EINVAL);
		goto put;
	}

	err = irblaster_set_default_state(pc, &args);
	if (err < 0) {
		pr_err("%s(): irblaster get state fail\n", __func__);
		pc = ERR_PTR(-EINVAL);
		goto put;
	}

	atomic_set(&pc->request, IRBLASTER_REQUESTED);
	irblasterchip_sysfs_unexport(pc);
put:
	of_node_put(args.np);

	return pc;
}
EXPORT_SYMBOL_GPL(of_irblaster_get);

static void devm_irblaster_release(struct device *dev, void *res)
{
	irblaster_put(*(struct irblaster_chip **)res);
}

/**
 * devm_of_irblaster_get() - resource managed of_irblaster_get()
 * @dev: device for irblaster consumer
 * @np: device node to get the irblaster from
 * @con_id: consumer name
 *
 * This function performs like of_irblaster_get() but the acquired irblaster
 * device will automatically be released on driver detach.
 *
 * Returns: A pointer to the requested irblaster device or an ERR_PTR()-encoded
 * error code on failure.
 */
struct irblaster_chip *devm_of_irblaster_get(struct device *dev,
					     struct device_node *np,
					     const char *con_id)
{
	struct irblaster_chip **dr, *chip;

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

	chip = of_irblaster_get(np, con_id);
	if (!IS_ERR(chip)) {
		*dr = chip;
		devres_add(dev, dr);
	} else {
		devres_free(dr);
	}

	return chip;
}
EXPORT_SYMBOL_GPL(devm_of_irblaster_get);
