/*
 * Synaptics DSX touchscreen driver
 *
 * Copyright (C) 2012-2016 Synaptics Incorporated. All rights reserved.
 *
 * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
 * Copyright (C) 2012 Alexandra Chin <alexandra.chin@tw.synaptics.com>
 * Copyright (C) 2012 Scott Lin <scott.lin@tw.synaptics.com>
 *
 * 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.
 *
 * INFORMATION CONTAINED IN THIS DOCUMENT IS PROVIDED "AS-IS," AND SYNAPTICS
 * EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
 * AND ANY WARRANTIES OF NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS.
 * IN NO EVENT SHALL SYNAPTICS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OF THE INFORMATION CONTAINED IN THIS DOCUMENT, HOWEVER CAUSED
 * AND BASED ON ANY THEORY OF LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, AND EVEN IF SYNAPTICS WAS ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE. IF A TRIBUNAL OF COMPETENT JURISDICTION DOES
 * NOT PERMIT THE DISCLAIMER OF DIRECT DAMAGES OR ANY OTHER DAMAGES, SYNAPTICS'
 * TOTAL CUMULATIVE LIABILITY TO ANY PARTY SHALL NOT EXCEED ONE HUNDRED U.S.
 * DOLLARS.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/input/synaptics_dsx.h>
#include "synaptics_dsx_core.h"

#define SYSFS_FOLDER_NAME "video"

static ssize_t video_sysfs_dcs_write_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count);

static ssize_t video_sysfs_param_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count);

static int video_send_dcs_command(unsigned char command_opcode);

struct f38_command {
	union {
		struct {
			unsigned char command_opcode;
			unsigned char register_access:1;
			unsigned char gamma_page:1;
			unsigned char f38_control1_b2__7:6;
			unsigned char parameter_field_1;
			unsigned char parameter_field_2;
			unsigned char parameter_field_3;
			unsigned char parameter_field_4;
			unsigned char send_to_dcs:1;
			unsigned char f38_command6_b1__7:7;
		} __packed;
		unsigned char data[7];
	};
};

struct synaptics_rmi4_video_handle {
	unsigned char param;
	unsigned short query_base_addr;
	unsigned short control_base_addr;
	unsigned short data_base_addr;
	unsigned short command_base_addr;
	struct synaptics_rmi4_data *rmi4_data;
	struct kobject *sysfs_dir;
};

#ifdef RMI_DCS_SUSPEND_RESUME
struct dcs_command {
	unsigned char command;
	unsigned int wait_time;
};

static struct dcs_command suspend_sequence[] = {
	{
		.command = 0x28,
		.wait_time = 200,
	},
	{
		.command = 0x10,
		.wait_time = 200,
	},
};

static struct dcs_command resume_sequence[] = {
	{
		.command = 0x11,
		.wait_time = 200,
	},
	{
		.command = 0x29,
		.wait_time = 200,
	},
};
#endif

static struct device_attribute attrs[] = {
	__ATTR(dcs_write, 0220,
			synaptics_rmi4_show_error,
			video_sysfs_dcs_write_store),
	__ATTR(param, 0220,
			synaptics_rmi4_show_error,
			video_sysfs_param_store),
};

static struct synaptics_rmi4_video_handle *video;

DECLARE_COMPLETION(video_remove_complete);

static ssize_t video_sysfs_dcs_write_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	int retval;
	unsigned int input;

	if (kstrtouint(buf, 16, &input) != 1)
		return -EINVAL;

	retval = video_send_dcs_command((unsigned char)input);
	if (retval < 0)
		return retval;

	return count;
}

static ssize_t video_sysfs_param_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	unsigned int input;

	if (kstrtouint(buf, 16, &input) != 1)
		return -EINVAL;

	video->param = (unsigned char)input;

	return count;
}

static int video_send_dcs_command(unsigned char command_opcode)
{
	int retval;
	struct f38_command command;
	struct synaptics_rmi4_data *rmi4_data = video->rmi4_data;

	memset(&command, 0x00, sizeof(command));

	command.command_opcode = command_opcode;
	command.parameter_field_1 = video->param;
	command.send_to_dcs = 1;

	video->param = 0;

	retval = synaptics_rmi4_reg_write(rmi4_data,
			video->command_base_addr,
			command.data,
			sizeof(command.data));
	if (retval < 0) {
		dev_err(rmi4_data->pdev->dev.parent,
				"%s: Failed to send DCS command\n",
				__func__);
		return retval;
	}

	return 0;
}

static int video_scan_pdt(void)
{
	int retval;
	unsigned char page;
	unsigned short addr;
	bool f38_found = false;
	struct synaptics_rmi4_fn_desc rmi_fd;
	struct synaptics_rmi4_data *rmi4_data = video->rmi4_data;

	for (page = 0; page < PAGES_TO_SERVICE; page++) {
		for (addr = PDT_START; addr > PDT_END; addr -= PDT_ENTRY_SIZE) {
			addr |= (page << 8);

			retval = synaptics_rmi4_reg_read(rmi4_data,
					addr,
					(unsigned char *)&rmi_fd,
					sizeof(rmi_fd));
			if (retval < 0)
				return retval;

			addr &= ~(MASK_8BIT << 8);

			if (!rmi_fd.fn_number)
				break;

			if (rmi_fd.fn_number == SYNAPTICS_RMI4_F38) {
				f38_found = true;
				goto f38_found;
			}
		}
	}

	if (!f38_found) {
		dev_err(rmi4_data->pdev->dev.parent,
				"%s: Failed to find F38\n",
				__func__);
		return -EINVAL;
	}

f38_found:
	video->query_base_addr = rmi_fd.query_base_addr | (page << 8);
	video->control_base_addr = rmi_fd.ctrl_base_addr | (page << 8);
	video->data_base_addr = rmi_fd.data_base_addr | (page << 8);
	video->command_base_addr = rmi_fd.cmd_base_addr | (page << 8);

	return 0;
}

static int synaptics_rmi4_video_init(struct synaptics_rmi4_data *rmi4_data)
{
	int retval;
	unsigned char attr_count;

	if (video) {
		dev_dbg(rmi4_data->pdev->dev.parent,
				"%s: Handle already exists\n",
				__func__);
		return 0;
	}

	video = kzalloc(sizeof(*video), GFP_KERNEL);
	if (!video) {
		dev_err(rmi4_data->pdev->dev.parent,
				"%s: Failed to alloc mem for video\n",
				__func__);
		retval = -ENOMEM;
		goto exit;
	}

	video->rmi4_data = rmi4_data;

	retval = video_scan_pdt();
	if (retval < 0) {
		retval = 0;
		goto exit_scan_pdt;
	}

	video->sysfs_dir = kobject_create_and_add(SYSFS_FOLDER_NAME,
			&rmi4_data->input_dev->dev.kobj);
	if (!video->sysfs_dir) {
		dev_err(rmi4_data->pdev->dev.parent,
				"%s: Failed to create sysfs directory\n",
				__func__);
		retval = -ENODEV;
		goto exit_sysfs_dir;
	}

	for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++) {
		retval = sysfs_create_file(video->sysfs_dir,
				&attrs[attr_count].attr);
		if (retval < 0) {
			dev_err(rmi4_data->pdev->dev.parent,
					"%s: Failed to create sysfs attributes\n",
					__func__);
			retval = -ENODEV;
			goto exit_sysfs_attrs;
		}
	}

	return 0;

exit_sysfs_attrs:
	for (attr_count--; attr_count >= 0; attr_count--)
		sysfs_remove_file(video->sysfs_dir, &attrs[attr_count].attr);

	kobject_put(video->sysfs_dir);

exit_sysfs_dir:
exit_scan_pdt:
	kfree(video);
	video = NULL;

exit:
	return retval;
}

static void synaptics_rmi4_video_remove(struct synaptics_rmi4_data *rmi4_data)
{
	unsigned char attr_count;

	if (!video)
		goto exit;

	for (attr_count = 0; attr_count < ARRAY_SIZE(attrs); attr_count++)
		sysfs_remove_file(video->sysfs_dir, &attrs[attr_count].attr);

	kobject_put(video->sysfs_dir);

	kfree(video);
	video = NULL;

exit:
	complete(&video_remove_complete);
}

static void synaptics_rmi4_video_reset(struct synaptics_rmi4_data *rmi4_data)
{
	if (!video)
		synaptics_rmi4_video_init(rmi4_data);
}

#ifdef RMI_DCS_SUSPEND_RESUME
static void synaptics_rmi4_video_suspend(struct synaptics_rmi4_data *rmi4_data)
{
	int retval;
	unsigned char ii;
	unsigned char command;
	unsigned char num_of_cmds;

	if (!video)
		return;

	num_of_cmds = ARRAY_SIZE(suspend_sequence);

	for (ii = 0; ii < num_of_cmds; ii++) {
		command = suspend_sequence[ii].command;
		retval = video_send_dcs_command(command);
		if (retval < 0)
			return;
		msleep(suspend_sequence[ii].wait_time);
	}
}

static void synaptics_rmi4_video_resume(struct synaptics_rmi4_data *rmi4_data)
{
	int retval;
	unsigned char ii;
	unsigned char command;
	unsigned char num_of_cmds;

	if (!video)
		return;

	num_of_cmds = ARRAY_SIZE(resume_sequence);

	for (ii = 0; ii < num_of_cmds; ii++) {
		command = resume_sequence[ii].command;
		retval = video_send_dcs_command(command);
		if (retval < 0)
			return;
		msleep(resume_sequence[ii].wait_time);
	}
}
#endif

static struct synaptics_rmi4_exp_fn video_module = {
	.fn_type = RMI_VIDEO,
	.init = synaptics_rmi4_video_init,
	.remove = synaptics_rmi4_video_remove,
	.reset = synaptics_rmi4_video_reset,
	.reinit = NULL,
	.early_suspend = NULL,
#ifdef RMI_DCS_SUSPEND_RESUME
	.suspend = synaptics_rmi4_video_suspend,
	.resume = synaptics_rmi4_video_resume,
#else
	.suspend = NULL,
	.resume = NULL,
#endif
	.late_resume = NULL,
	.attn = NULL,
};

static int __init rmi4_video_module_init(void)
{
	synaptics_rmi4_new_function(&video_module, true);

	return 0;
}

static void __exit rmi4_video_module_exit(void)
{
	synaptics_rmi4_new_function(&video_module, false);

	wait_for_completion(&video_remove_complete);
}

module_init(rmi4_video_module_init);
module_exit(rmi4_video_module_exit);

MODULE_AUTHOR("Synaptics, Inc.");
MODULE_DESCRIPTION("Synaptics DSX Video Module");
MODULE_LICENSE("GPL v2");
