/*
 * Realtek Semiconductor Corp.
 *
 * rts_camera_hx280enc.c
 *
 * Copyright (C) 2014      Ming Qian<ming_qian@realsil.com.cn>
 * Ming Qian, Realsil Software Engineering, <ming_qian@realsil.com.cn>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
--------------------------------------------------------------------------------
--
--  Abstract : 6280/7280/8270/8290/H1 Encoder device driver (kernel module)
--
------------------------------------------------------------------------------
 */

#define TAG	"H264"
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/reset.h>
#include <linux/clk.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_irq.h>

#include "linux/rts_camera_hx280enc.h"
#include "rts_camera.h"
#include "rts_hw_id.h"
#include "rts_isp_mem.h"

#define RTS_H264_HX280_DRV_NAME		"rts_h264_hx280enc"
#define RTS_H264_HX280_DEV_NAME		"rtshx280enc"

#define H264_FRMAE_DONE_BIT		0

#define ENC_HW_ID1			0x62800000
#define ENC_HW_ID2			0x72800000
#define ENC_HW_ID3			0x82700000
#define ENC_HW_ID4			0x82900000
#define ENC_HW_ID5			0x48310000

#define RTS_H264_CLOCK_ON_NEED		1

struct rtscam_hx280_enc {
	struct device *dev;
	void __iomem *hwregs;
	unsigned long iobaseaddr;
	unsigned int iosize;

	struct rtscam_ge_device *hdev;
	atomic_t use_count;

	int clk_state;

	struct mutex lock;

	unsigned long status;
	wait_queue_head_t alarm_wq;

	struct clk *clk;
	struct reset_control *reset;
	unsigned long clk_rate;
#ifdef DBG_ENCODE_TIME
	struct ring_buffer *timebuf;
	struct timespec start;
	struct timespec end;
	unsigned long cnt;
	int open_count;
	int wid;
#endif
};

static void rtscam_hx280_update_clk_rate(struct rtscam_hx280_enc *rh264enc)
{
	unsigned long rate;

	rate = clk_get_rate(rh264enc->clk);
	if (rh264enc->clk_rate && rh264enc->clk_rate != rate)
		clk_set_rate(rh264enc->clk, rh264enc->clk_rate);

	rh264enc->clk_rate = clk_get_rate(rh264enc->clk);
	if (rh264enc->clk_rate == rate)
		return;

	rtsprintk(RTS_TRACE_INFO, "h264 clk: %ld -> %ld\n",
		  rate, rh264enc->clk_rate);
}

static int rtscam_hx280_enable_clk(struct rtscam_hx280_enc *rh264enc,
				   int enable)
{
	if (!rh264enc || IS_ERR(rh264enc->clk) || IS_ERR(rh264enc->reset))
		return -EINVAL;

	if (enable) {

		if (RTSCAM_STATE_ACTIVE == rh264enc->clk_state)
			return 0;

		reset_control_reset(rh264enc->reset);
		udelay(1);

		rtscam_hx280_update_clk_rate(rh264enc);
		clk_prepare_enable(rh264enc->clk);

		rh264enc->clk_state = RTSCAM_STATE_ACTIVE;
	} else {
		if (RTSCAM_STATE_PASSIVE == rh264enc->clk_state)
			return 0;

		clk_disable_unprepare(rh264enc->clk);

		rh264enc->clk_state = RTSCAM_STATE_PASSIVE;
	}

	return 0;
}

static void rtscam_hx280_reset_asic(struct rtscam_hx280_enc *rh264enc)
{
	int i;

	iowrite32(cpu_to_le32(0), rh264enc->hwregs + 0x38);

	for (i = 4; i < rh264enc->iosize; i += 4)
		iowrite32(cpu_to_le32(0), rh264enc->hwregs + i);
}

#ifdef DBG_ENCODE_TIME
static int __init_timebuf(struct rtscam_hx280_enc *rh264enc)
{
	if (rtscam_init_ring_buf(rh264enc->timebuf) < 0)
		return -EINVAL;

	if (!rh264enc->open_count)
		rh264enc->cnt = 0;
	rh264enc->open_count++;
	return 0;
}

static void __set_start_encode_time(struct rtscam_hx280_enc *rh264enc,
						int width)
{
	if (rh264enc->start.tv_sec) {
		long delta;

		delta = (rh264enc->end.tv_nsec - rh264enc->start.tv_nsec) /
			1000 / 1000 +
			(rh264enc->end.tv_sec - rh264enc->start.tv_sec) * 1000;

		rtscam_write_ring_buf(rh264enc->timebuf, rh264enc->wid,
						delta, rh264enc->cnt++);
	}
	rh264enc->wid = width;
	getrawmonotonic(&rh264enc->start);
}

static int __alloc_time_buf(struct rtscam_hx280_enc *rh264enc)
{
	rh264enc->timebuf = kmalloc(sizeof(*rh264enc->timebuf),
						GFP_KERNEL);
	if (!rh264enc->timebuf) {
		rtsprintk(RTS_TRACE_ERROR, "fail to alloc timebuf mem\n");
		return -ENOMEM;
	}

	return 0;
}

static void __put_time_buf(struct rtscam_hx280_enc *rh264enc)
{
	kfree(rh264enc->timebuf);
}

#else

static int __init_timebuf(struct rtscam_hx280_enc *rh264enc)
{
	return 0;
}

static void __set_start_encode_time(struct rtscam_hx280_enc *rh264enc,
						int width)
{
}

static int __alloc_time_buf(struct rtscam_hx280_enc *rh264enc)
{
	return 0;
}

static void __put_time_buf(struct rtscam_hx280_enc *rh264enc)
{
}
#endif

static int rtscam_hx280_open(struct file *filp)
{
	struct rtscam_ge_device *gdev = rtscam_devdata(filp);
	struct rtscam_hx280_enc *rh264enc = rtscam_ge_get_drvdata(gdev);

	if (__init_timebuf(rh264enc) < 0)
		return -EINVAL;

	filp->private_data = rh264enc;
	return 0;
}

static int rtscam_hx280_close(struct file *filp)
{
	struct rtscam_hx280_enc *rh264enc = filp->private_data;

	filp->private_data = NULL;

	if (!rh264enc)
		return -EINVAL;

#ifdef DBG_ENCODE_TIME
	rh264enc->open_count--;
#endif

	return 0;
}

static long rtscam_hx280_do_ioctl(struct file *filp, unsigned int cmd,
				  void *arg)
{
	struct rtscam_hx280_enc *rh264enc = filp->private_data;
	int err = 0;

	if (!rh264enc)
		return -EINVAL;

	rtsprintk(RTS_TRACE_DEBUG, "ioctl cmd 0x%08x, '%c' %d\n",
		  cmd, _IOC_TYPE(cmd), _IOC_NR(cmd));

	/*
	 * extract the type and number bitfields, and don't encode
	 * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
	 */
	if (_IOC_TYPE(cmd) != HX280ENC_IOC_MAGIC)
		return -ENOTTY;
	if (_IOC_NR(cmd) > HX280ENC_IOC_MAXNR)
		return -ENOTTY;

	switch (cmd) {
	case HX280ENC_IOCGHWOFFSET:
		*(unsigned long *)arg = rh264enc->iobaseaddr;
		break;
	case HX280ENC_IOCGHWIOSIZE:
		*(unsigned int *)arg = rh264enc->iosize;
		break;
	case HX280ENC_IOC_DONE:
		clear_bit(H264_FRMAE_DONE_BIT, &rh264enc->status);
		break;
	case HX280ENC_IOC_ENABLE:
#if RTS_H264_CLOCK_ON_NEED
		mutex_lock(&rh264enc->lock);
		if (atomic_inc_return(&rh264enc->use_count) == 1)
			rtscam_hx280_enable_clk(rh264enc, 1);
		mutex_unlock(&rh264enc->lock);
#endif
		break;
	case HX280ENC_IOC_DISABLE:
#if RTS_H264_CLOCK_ON_NEED
		mutex_lock(&rh264enc->lock);
		if (atomic_dec_return(&rh264enc->use_count) == 0)
			rtscam_hx280_enable_clk(rh264enc, 0);
		mutex_unlock(&rh264enc->lock);
#endif
		break;
	case HX280ENC_IOC_SENCODE_TIME:
		__set_start_encode_time(rh264enc, (int)arg);
		break;
	case HX280ENC_IOC_GENCODE_TIMEBUF:
#ifdef DBG_ENCODE_TIME
		err = rtscam_read_ring_buf(rh264enc->timebuf,
						(void __user *)arg);
#else
		err = -ENOTTY;
#endif
		break;
	default:
		rtsprintk(RTS_TRACE_ERROR,
			  "unknown[h264] ioctl 0x%08x, '%c' 0x%x\n",
			  cmd, _IOC_TYPE(cmd), _IOC_NR(cmd));
		err = -ENOTTY;
		break;
	}

	return err;
}

static long rtscam_hx280_ioctl(struct file *filp, unsigned int cmd,
			       unsigned long arg)
{
	return rtscam_usercopy(filp, cmd, arg, rtscam_hx280_do_ioctl);
}

static unsigned int rtscam_hx280_poll(struct file *filp,
				      struct poll_table_struct *wait)
{
	struct rtscam_hx280_enc *rh264enc = filp->private_data;
	unsigned int mask = 0;
	unsigned long req_events = poll_requested_events(wait);

	if (!(req_events & (POLLIN | POLLRDNORM)))
		return mask;

	if (!test_bit(H264_FRMAE_DONE_BIT, &rh264enc->status))
		poll_wait(filp, &rh264enc->alarm_wq, wait);
	else
		return POLLIN | POLLRDNORM;

	if (test_bit(H264_FRMAE_DONE_BIT, &rh264enc->status))
		mask = POLLIN | POLLRDNORM;

	return mask;
}

static int rtscam_hx280_mmap(struct file *filp, struct vm_area_struct *vm)
{
	struct rtscam_hx280_enc *rh264enc = filp->private_data;
	unsigned long addr, size, start, end;

	addr = vm->vm_pgoff << PAGE_SHIFT;
	size = vm->vm_end - vm->vm_start;
	start = rh264enc->iobaseaddr;
	end = start + PAGE_ALIGN(rh264enc->iosize);

	if (addr < start || (addr + size) > end)
		return -EINVAL;

	vm->vm_page_prot = pgprot_noncached(vm->vm_page_prot);

	return remap_pfn_range(vm, vm->vm_start, vm->vm_pgoff,
			size, vm->vm_page_prot) ? -EAGAIN : 0;
}

static struct rtscam_ge_file_operations rtscam_hx280_fops = {
	.owner		= THIS_MODULE,
	.open		= rtscam_hx280_open,
	.release	= rtscam_hx280_close,
	.ioctl		= rtscam_hx280_ioctl,
	.poll		= rtscam_hx280_poll,
	.mmap		= rtscam_hx280_mmap,
};

static int __create_device(struct rtscam_hx280_enc *rh264enc)
{
	struct rtscam_ge_device *gdev;
	int ret;

	if (rh264enc->hdev)
		return 0;

	gdev = rtscam_ge_device_alloc();
	if (!gdev)
		return -ENOMEM;

	strlcpy(gdev->name, RTS_H264_HX280_DEV_NAME, sizeof(gdev->name));
	gdev->parent = get_device(rh264enc->dev);
	gdev->release = rtscam_ge_device_release;
	gdev->fops = &rtscam_hx280_fops;

	rtscam_ge_set_drvdata(gdev, rh264enc);
	ret = rtscam_ge_register_device(gdev);
	if (ret) {
		rtscam_ge_device_release(gdev);
		return ret;
	}

	rh264enc->hdev = gdev;

	return 0;
}

static void __remove_device(struct rtscam_hx280_enc *rh264enc)
{
	struct rtscam_ge_device *gdev;

	if (!rh264enc->hdev)
		return;

	gdev = rh264enc->hdev;
	put_device(gdev->parent);
	rtscam_ge_unregister_device(gdev);

	rh264enc->hdev = NULL;
}

static irqreturn_t rtscam_hx280_irq(int irq, void *data)
{
	struct rtscam_hx280_enc *rh264enc = data;
	u32 irq_status;

	irq_status = le32_to_cpu(ioread32(rh264enc->hwregs + 0x04));

	if (!irq_status)
		return IRQ_NONE;

	if (irq_status & 0x01) {
#ifdef DBG_ENCODE_TIME
		getrawmonotonic(&rh264enc->end);
#endif
		iowrite32(cpu_to_le32(irq_status & (~0x101)),
			  rh264enc->hwregs + 0x04);

		/* Handle slice ready interrupts. The reference implementation
		 * doesn't signal slice ready interrupts to EWL.
		 * The EWL will poll the slices ready register value. */
		if ((irq_status & 0x1FE) == 0x100) {
			rtsprintk(RTS_TRACE_DEBUG,
				  "Slice ready IRQ handled!\n");
			return IRQ_HANDLED;
		}

		set_bit(H264_FRMAE_DONE_BIT, &rh264enc->status);
		/*wake_up(&rh264enc->alarm_wq);*/
		wake_up_interruptible(&rh264enc->alarm_wq);
		/* All other interrupts will be signaled to EWL. */
		rtscam_ge_kill_fasync(rh264enc->hdev, SIGIO, POLL_IN);

		return IRQ_HANDLED;
	}

	rtsprintk(RTS_TRACE_DEBUG, "IRQ received, but NOT handled!\n");
	return IRQ_NONE;
}

static int rtscam_hx280_reserve_io(struct rtscam_hx280_enc *rh264enc)
{
	u32 hwid = le32_to_cpu(ioread32(rh264enc->hwregs));

	if ((((hwid >> 16) & 0xFFFF) != ((ENC_HW_ID1 >> 16) & 0xFFFF)) &&
	    (((hwid >> 16) & 0xFFFF) != ((ENC_HW_ID2 >> 16) & 0xFFFF)) &&
	    (((hwid >> 16) & 0xFFFF) != ((ENC_HW_ID3 >> 16) & 0xFFFF)) &&
	    (((hwid >> 16) & 0xFFFF) != ((ENC_HW_ID4 >> 16) & 0xFFFF)) &&
	    (((hwid >> 16) & 0xFFFF) != ((ENC_HW_ID5 >> 16) & 0xFFFF))) {
		rtsprintk(RTS_TRACE_ERROR,
			  "hx280enc:HW not found at 0x%08lx, hwid = 0x%08x\n",
			  rh264enc->iobaseaddr, hwid);
		return -EBUSY;
	}

	rtsprintk(RTS_TRACE_ERROR,
		  "hx280enc:HW at base <0x%08lx> with ID <0x%08x>\n",
		  rh264enc->iobaseaddr, hwid);

	return 0;
}

static ssize_t rtscam_hx280_get_clk_rate(struct device *dev,
					 struct device_attribute *attr,
					 char *buf)
{
	struct rtscam_hx280_enc *rh264enc = dev_get_drvdata(dev);
	int num = 0;

	if (IS_ERR(rh264enc->clk))
		return 0;

	num += scnprintf(buf, PAGE_SIZE, "%lu\n", clk_get_rate(rh264enc->clk));

	return num;
}

static ssize_t rtscam_hx280_set_clk_rate(struct device *dev,
					 struct device_attribute *attr,
					 const char *buf, size_t count)
{
	unsigned long rate;
	struct rtscam_hx280_enc *rh264enc = dev_get_drvdata(dev);

	sscanf(buf, "%lu", &rate);

	if (rate)
		rh264enc->clk_rate = rate;

	return count;
}

static DEVICE_ATTR(clock, 0664,
		   rtscam_hx280_get_clk_rate, rtscam_hx280_set_clk_rate);

static int rtscam_hx280_probe(struct platform_device *pdev)
{
	struct rtscam_hx280_enc *rh264enc;
	struct resource *res;
	void __iomem *base;
	u32 val;
	int irq;
	int err = 0;

	rtsprintk(RTS_TRACE_INFO, "%s\n", __func__);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	irq = platform_get_irq(pdev, 0);

	if (NULL == res || irq < 0) {
		rtsprintk(RTS_TRACE_ERROR, "Missing platform resource data\n");
		return -ENODEV;
	}

	rh264enc = devm_kzalloc(&pdev->dev, sizeof(*rh264enc), GFP_KERNEL);
	if (NULL == rh264enc) {
		rtsprintk(RTS_TRACE_ERROR,
			  "Couldn't allocate rts camera h264enc object\n");
		return -ENOMEM;
	}

	rh264enc->dev = get_device(&pdev->dev);
	atomic_set(&rh264enc->use_count, 0);
	rh264enc->clk_state = RTSCAM_STATE_PASSIVE;
	mutex_init(&rh264enc->lock);
	init_waitqueue_head(&rh264enc->alarm_wq);

	base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(base)) {
		rtsprintk(RTS_TRACE_ERROR, "Couldn't ioremap resource\n");
		err = PTR_ERR(base);
		goto error;
	}
	rh264enc->hwregs = base;
	rh264enc->iobaseaddr = res->start;
	rh264enc->iosize = resource_size(res);

	err = devm_request_irq(rh264enc->dev, irq, rtscam_hx280_irq,
			       IRQF_SHARED, RTS_H264_HX280_DRV_NAME, rh264enc);
	if (err) {
		rtsprintk(RTS_TRACE_ERROR, "request irq failed\n");
		goto error;
	}

	rh264enc->clk = devm_clk_get(rh264enc->dev, "h264_clk");
	if (IS_ERR(rh264enc->clk)) {
		rtsprintk(RTS_TRACE_ERROR, "Couldn't get h264 clk\n");
		err = PTR_ERR(rh264enc->clk);
		goto error;
	}
	err = of_property_read_u32_index(pdev->dev.of_node, "clock-frequency",
					 0, &val);
	if (!err)
		rh264enc->clk_rate = val;
	else
		rh264enc->clk_rate = clk_get_rate(rh264enc->clk);
	rtsprintk(RTS_TRACE_INFO, "h264 clk default frequency is %ld\n",
		  rh264enc->clk_rate);
	rh264enc->reset = devm_reset_control_get(rh264enc->dev, "h264_rst");
	if (IS_ERR(rh264enc->reset)) {
		rtsprintk(RTS_TRACE_ERROR, "Couldn't get h264 reset\n");
		err = PTR_ERR(rh264enc->reset);
		goto error;
	}

	rtscam_hx280_enable_clk(rh264enc, 1);
	err = rtscam_hx280_reserve_io(rh264enc);
	if (!err)
		rtscam_hx280_reset_asic(rh264enc);
#if RTS_H264_CLOCK_ON_NEED
	rtscam_hx280_enable_clk(rh264enc, 0);
#endif

	if (err) {
		rtsprintk(RTS_TRACE_ERROR, "hx280enc: Reserve IO fail\n");
		goto error;
	}

	err = __alloc_time_buf(rh264enc);
	if (err)
		goto error;

	__create_device(rh264enc);

	platform_set_drvdata(pdev, rh264enc);
	device_create_file(rh264enc->dev, &dev_attr_clock);

	return 0;
error:
	if (rh264enc && rh264enc->dev) {
		put_device(rh264enc->dev);
		rh264enc->dev = NULL;
	}
	return err;
}

static int rtscam_hx280_remove(struct platform_device *pdev)
{
	struct rtscam_hx280_enc *rh264enc = platform_get_drvdata(pdev);

	device_remove_file(rh264enc->dev, &dev_attr_clock);

	__put_time_buf(rh264enc);
	__remove_device(rh264enc);
#if !RTS_H264_CLOCK_ON_NEED
	rtscam_hx280_enable_clk(rh264enc, 0);
#endif
	put_device(rh264enc->dev);
	rh264enc->dev = NULL;

	return 0;
}

static const struct of_device_id rtscam_hx280_ids[] = {
	{ .compatible = "realtek,rts3903-h264", },
	{ /* sentinel */ },
};

static struct platform_driver rtscam_hx280_driver = {
	.driver		= {
		.name	= RTS_H264_HX280_DRV_NAME,
		.owner	= THIS_MODULE,
		.of_match_table = of_match_ptr(rtscam_hx280_ids),
	},
	.probe		= rtscam_hx280_probe,
	.remove		= rtscam_hx280_remove,
};

module_platform_driver(rtscam_hx280_driver);

MODULE_DESCRIPTION("Realsil H264 encoder device driver");
MODULE_AUTHOR("Ming Qian <ming_qian@realsil.com.cn>");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("0.1.1");
MODULE_ALIAS("platform:" RTS_H264_HX280_DRV_NAME);
