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

#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/types.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/errno.h>
#include <asm/irq.h>
#include <linux/io.h>
#include <linux/major.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/pinctrl/consumer.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>

#include "meson_ir_main.h"

static ssize_t protocol_show(struct device *dev,
			     struct device_attribute *attr, char *buf)
{
	struct meson_ir_chip *chip = dev_get_drvdata(dev);

	if (ENABLE_LEGACY_IR(chip->protocol))
		return sprintf(buf, "protocol=%s&%s (0x%x)\n",
			       chip->ir_contr[LEGACY_IR_ID].proto_name,
			       chip->ir_contr[MULTI_IR_ID].proto_name,
			       chip->protocol);

	return sprintf(buf, "protocol=%s (0x%x)\n",
		       chip->ir_contr[MULTI_IR_ID].proto_name,
		       chip->protocol);
}

static ssize_t protocol_store(struct device *dev,
			      struct device_attribute *attr, const char *buf,
			      size_t count)
{
	int ret;
	int protocol;
	unsigned long flags;
	struct meson_ir_chip *chip = dev_get_drvdata(dev);

	ret = kstrtoint(buf, 0, &protocol);
	if (ret != 0) {
		dev_err(chip->dev, "input parameter error\n");
		return -EINVAL;
	}

	spin_lock_irqsave(&chip->slock, flags);
	chip->protocol = protocol;
	chip->set_register_config(chip, chip->protocol);
	spin_unlock_irqrestore(&chip->slock, flags);

	if (MULTI_IR_SOFTWARE_DECODE(chip->r_dev->rc_type) &&
	    !MULTI_IR_SOFTWARE_DECODE(chip->protocol)) {
		meson_ir_raw_event_unregister(chip->r_dev); /*raw->no raw*/
		dev_info(chip->dev, "meson_ir_raw_event_unregister\n");
	} else if (!MULTI_IR_SOFTWARE_DECODE(chip->r_dev->rc_type) &&
		  MULTI_IR_SOFTWARE_DECODE(chip->protocol)) {
		meson_ir_raw_event_register(chip->r_dev); /*no raw->raw*/
	}
	chip->r_dev->rc_type = chip->protocol;
	return count;
}

static ssize_t keymap_show(struct device *dev,
			   struct device_attribute *attr, char *buf)
{
	struct meson_ir_chip *chip = dev_get_drvdata(dev);
	struct meson_ir_map_tab_list *map_tab;
	unsigned long flags;
	int i, len;

	spin_lock_irqsave(&chip->slock, flags);
	map_tab = meson_ir_seek_map_tab(chip, chip->sys_custom_code);
	if (!map_tab) {
		dev_err(chip->dev, "please set valid keymap name first\n");
		spin_unlock_irqrestore(&chip->slock, flags);
		return 0;
	}
	len = sprintf(buf, "custom_code=0x%x\n", map_tab->tab.custom_code);
	len += sprintf(buf + len, "custom_name=%s\n", map_tab->tab.custom_name);
	len += sprintf(buf + len, "release_delay=%d\n",
			map_tab->tab.release_delay);
	len += sprintf(buf + len, "map_size=%d\n", map_tab->tab.map_size);
	len += sprintf(buf + len, "fn_key_scancode = 0x%x\n",
			map_tab->tab.cursor_code.fn_key_scancode);
	len += sprintf(buf + len, "cursor_left_scancode = 0x%x\n",
			map_tab->tab.cursor_code.cursor_left_scancode);
	len += sprintf(buf + len, "cursor_right_scancode = 0x%x\n",
			map_tab->tab.cursor_code.cursor_right_scancode);
	len += sprintf(buf + len, "cursor_up_scancode = 0x%x\n",
			map_tab->tab.cursor_code.cursor_up_scancode);
	len += sprintf(buf + len, "cursor_down_scancode = 0x%x\n",
			map_tab->tab.cursor_code.cursor_down_scancode);
	len += sprintf(buf + len, "cursor_ok_scancode = 0x%x\n",
			map_tab->tab.cursor_code.cursor_ok_scancode);
	len += sprintf(buf + len, "keycode scancode\n");
	for (i = 0; i <  map_tab->tab.map_size; i++) {
		len += sprintf(buf + len, "%4d %4d\n",
			       map_tab->tab.codemap[i].map.keycode,
			       map_tab->tab.codemap[i].map.scancode);
	}
	spin_unlock_irqrestore(&chip->slock, flags);
	return len;
}

static ssize_t keymap_store(struct device *dev,
			    struct device_attribute *attr, const char *buf,
			    size_t count)
{
	struct meson_ir_chip *chip = dev_get_drvdata(dev);
	int ret;
	int value;

	ret = kstrtoint(buf, 0, &value);
	if (ret != 0) {
		dev_err(chip->dev, "keymap store input err\n");
		return -EINVAL;
	}
	chip->sys_custom_code = value;
	return count;
}

static ssize_t debug_enable_show(struct device *dev,
				 struct device_attribute *attr, char *buf)
{
	struct meson_ir_chip *chip = dev_get_drvdata(dev);
	struct meson_ir_dev  *r_dev = chip->r_dev;

	return sprintf(buf, "%d\n", r_dev->debug_enable);
}

static ssize_t debug_enable_store(struct device *dev,
				  struct device_attribute *attr,
				  const char *buf, size_t count)
{
	struct meson_ir_chip *chip = dev_get_drvdata(dev);
	struct meson_ir_dev  *r_dev = chip->r_dev;
	int debug_enable;
	int ret;

	ret = kstrtoint(buf, 0, &debug_enable);
	if (ret != 0)
		return -EINVAL;
	r_dev->debug_enable = debug_enable;
	return count;
}

static ssize_t enable_show(struct device *dev,
			   struct device_attribute *attr, char *buf)
{
	struct meson_ir_chip *chip = dev_get_drvdata(dev);
	struct meson_ir_dev  *r_dev = chip->r_dev;

	return sprintf(buf, "%d\n", r_dev->enable);
}

static ssize_t enable_store(struct device *dev,
			    struct device_attribute *attr,
			    const char *buf, size_t count)
{
	struct meson_ir_chip *chip = dev_get_drvdata(dev);
	struct meson_ir_dev  *r_dev = chip->r_dev;
	unsigned int val;
	int enable;
	int ret;
	int id;

	ret = kstrtoint(buf, 0, &enable);
	if (ret != 0 || (enable != 0 && enable != 1))
		return -EINVAL;

	for (id = 0; id < (ENABLE_LEGACY_IR(chip->protocol) ? 2 : 1); id++) {
		if (enable) {
			regmap_read(chip->ir_contr[id].base, REG_FRAME, &val);
			regmap_update_bits(chip->ir_contr[id].base, REG_REG1,
					   BIT(15), BIT(15));
		} else {
			regmap_update_bits(chip->ir_contr[id].base, REG_REG1,
					   BIT(15), 0);
		}
	}
	r_dev->enable = enable;

	return count;
}

static ssize_t map_tables_show(struct device *dev,
			       struct device_attribute *attr, char *buf)
{
	struct meson_ir_chip *chip = dev_get_drvdata(dev);
	struct meson_ir_map_tab_list *ir_map;
	unsigned long flags;
	int len = 0;
	int cnt = 0;

	spin_lock_irqsave(&chip->slock, flags);
	list_for_each_entry(ir_map, &chip->map_tab_head, list) {
		len += sprintf(buf + len, "%d. 0x%x,%s\n", cnt,
			      ir_map->tab.custom_code,
			      ir_map->tab.custom_name);
		cnt++;
	}
	spin_unlock_irqrestore(&chip->slock, flags);
	return len;
}

static ssize_t led_blink_show(struct device *dev, struct device_attribute *attr,
			      char *buf)
{
	struct meson_ir_chip *chip = dev_get_drvdata(dev);
	struct meson_ir_dev  *r_dev = chip->r_dev;

	return sprintf(buf, "%u\n", r_dev->led_blink);
}

static ssize_t led_blink_store(struct device *dev,
			       struct device_attribute *attr, const char *buf,
			       size_t count)
{
	int ret = 0;
	int val = 0;

	struct meson_ir_chip *chip = dev_get_drvdata(dev);
	struct meson_ir_dev  *r_dev = chip->r_dev;

	ret = kstrtoint(buf, 0, &val);
	if (ret != 0)
		return -EINVAL;
	r_dev->led_blink = val;
	return count;
}

static ssize_t led_frq_show(struct device *dev, struct device_attribute *attr,
			    char *buf)
{
	struct meson_ir_chip *chip = dev_get_drvdata(dev);
	struct meson_ir_dev  *r_dev = chip->r_dev;

	return sprintf(buf, "%ld\n", r_dev->delay_on);
}

static ssize_t led_frq_store(struct device *dev, struct device_attribute *attr,
			     const char *buf, size_t count)
{
	int ret = 0;
	int val = 0;

	struct meson_ir_chip *chip = dev_get_drvdata(dev);
	struct meson_ir_dev  *r_dev = chip->r_dev;

	ret = kstrtoint(buf, 0, &val);
	if (ret != 0)
		return -EINVAL;
	r_dev->delay_off = val;
	r_dev->delay_on = val;
	return count;
}

static ssize_t ir_learning_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	struct meson_ir_chip *chip = dev_get_drvdata(dev);
	struct meson_ir_dev  *r_dev = chip->r_dev;

	return sprintf(buf, "%d\n", r_dev->ir_learning_on);
}

static ssize_t ir_learning_store(struct device *dev,
				 struct device_attribute *attr,
				 const char *buf, size_t count)
{
	int ret = 0;
	int val = 0;
	struct meson_ir_chip *chip = dev_get_drvdata(dev);
	struct meson_ir_dev  *r_dev = chip->r_dev;

	ret = kstrtoint(buf, 0, &val);
	if (ret != 0)
		return -EINVAL;

	if (r_dev->ir_learning_on == !!val)
		return count;

	disable_irq(chip->irqno);
	mutex_lock(&chip->file_lock);
	r_dev->ir_learning_on = !!val;
	if (!!val) {
		chip->set_register_config(chip, REMOTE_TYPE_RAW_NEC);
		r_dev->protocol = chip->protocol;
		chip->protocol = REMOTE_TYPE_RAW_NEC;
		if (meson_ir_pulses_malloc(chip) < 0) {
			mutex_unlock(&chip->file_lock);
			enable_irq(chip->irqno);
			return -ENOMEM;
		}
	} else {
		chip->protocol = r_dev->protocol;
		chip->set_register_config(chip, chip->protocol);
		meson_ir_pulses_free(chip);
	}
	mutex_unlock(&chip->file_lock);
	enable_irq(chip->irqno);
	return count;
}

static ssize_t learned_pulse_show(struct device *dev,
				  struct device_attribute *attr, char *buf)
{
	int len = 0;
	int i = 0;
	struct meson_ir_chip *chip = dev_get_drvdata(dev);
	struct meson_ir_dev  *r_dev = chip->r_dev;

	if (!r_dev->pulses)
		return len;

	disable_irq(chip->irqno);
	mutex_lock(&chip->file_lock);

	for (i = 0; i < r_dev->pulses->len; i++)
		len += sprintf(buf + len, "%lds",
			       r_dev->pulses->pulse[i] & GENMASK(30, 0));

	mutex_unlock(&chip->file_lock);
	enable_irq(chip->irqno);
	len += sprintf(buf + len, "\n");

	return len;
}

static ssize_t learned_pulse_store(struct device *dev,
				   struct device_attribute *attr,
				   const char *buf, size_t count)
{
	int len = 0;

	struct meson_ir_chip *chip = dev_get_drvdata(dev);
	struct meson_ir_dev  *r_dev = chip->r_dev;

	if (!r_dev->pulses)
		return len;

	/*clear learned pulse*/
	if (buf[0] == 'c')
		memset(r_dev->pulses, 0, sizeof(struct pulse_group) +
		       r_dev->max_learned_pulse * sizeof(u32));

	return count;
}
DEVICE_ATTR_RW(learned_pulse);
DEVICE_ATTR_RW(ir_learning);
DEVICE_ATTR_RW(led_frq);
DEVICE_ATTR_RW(led_blink);
DEVICE_ATTR_RW(protocol);
DEVICE_ATTR_RW(keymap);
DEVICE_ATTR_RW(debug_enable);
DEVICE_ATTR_RW(enable);
DEVICE_ATTR_RO(map_tables);

static struct attribute *meson_ir_sysfs_attrs[] = {
	&dev_attr_protocol.attr,
	&dev_attr_map_tables.attr,
	&dev_attr_keymap.attr,
	&dev_attr_debug_enable.attr,
	&dev_attr_enable.attr,
	&dev_attr_led_blink.attr,
	&dev_attr_led_frq.attr,
	&dev_attr_ir_learning.attr,
	&dev_attr_learned_pulse.attr,
	NULL,
};

ATTRIBUTE_GROUPS(meson_ir_sysfs);

static struct class meson_ir_class = {
	.name		= "remote",
	.owner		= THIS_MODULE,
	.dev_groups = meson_ir_sysfs_groups,
};

int meson_ir_sysfs_init(struct meson_ir_chip *chip)
{
	struct device *dev;
	int err;

	err = class_register(&meson_ir_class);
	if (unlikely(err))
		return err;

	dev = device_create(&meson_ir_class,  NULL, chip->chr_devno, chip,
			    chip->dev_name);
	if (IS_ERR(dev))
		return PTR_ERR(dev);
	return 0;
}
EXPORT_SYMBOL_GPL(meson_ir_sysfs_init);

void meson_ir_sysfs_exit(struct meson_ir_chip *chip)
{
	device_destroy(&meson_ir_class, chip->chr_devno);
	class_unregister(&meson_ir_class);
}
EXPORT_SYMBOL_GPL(meson_ir_sysfs_exit);
