/*
 *  Copyright (C) 2020 Realsil Microelectronics Inc.
 *  All Rights Reserved
 *
 * 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.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>

#define DDRLOCK_CTRL		(0x60)
#define DDRLOCK_START		(0x64)
#define DDRLOCK_END		(0x68)

struct rts_ddrlock {
	struct device *dev;
	void __iomem *addr;
	struct resource *mem;
};


static ssize_t get_ddrlock_ctrl(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct rts_ddrlock *ddrlock = dev_get_drvdata(dev);
	int num = 0;
	u32 val;

	val = readl(ddrlock->addr + DDRLOCK_CTRL);
	num += scnprintf(buf + num, PAGE_SIZE, "%d\n", (int)val);

	return num;
}

static ssize_t set_ddrlock_ctrl(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	struct rts_ddrlock *ddrlock = dev_get_drvdata(dev);
	u32 val;

	if (kstrtou32(buf, 0, &val) < 0)
		return 0;
	writel(val, ddrlock->addr + DDRLOCK_CTRL);

	return count;
}

static ssize_t get_ddrlock_start(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct rts_ddrlock *ddrlock = dev_get_drvdata(dev);
	int num = 0;
	u32 val;

	val = readl(ddrlock->addr + DDRLOCK_START);
	num += scnprintf(buf + num, PAGE_SIZE, "0x%08x\n", val);

	return num;
}

static ssize_t set_ddrlock_start(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	struct rts_ddrlock *ddrlock = dev_get_drvdata(dev);
	u32 val;

	if (kstrtou32(buf, 0, &val) < 0)
		return 0;
	writel(val, ddrlock->addr + DDRLOCK_START);

	return count;
}

static ssize_t get_ddrlock_end(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct rts_ddrlock *ddrlock = dev_get_drvdata(dev);
	int num = 0;
	u32 val;

	val = readl(ddrlock->addr + DDRLOCK_END);
	num += scnprintf(buf + num, PAGE_SIZE, "0x%08x\n", val);

	return num;
}

static ssize_t set_ddrlock_end(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	struct rts_ddrlock *ddrlock = dev_get_drvdata(dev);
	u32 val;

	if (kstrtou32(buf, 0, &val) < 0)
		return 0;
	writel(val, ddrlock->addr + DDRLOCK_END);

	return count;
}

static DEVICE_ATTR(ddrlock_ctrl, 0664, get_ddrlock_ctrl, set_ddrlock_ctrl);
static DEVICE_ATTR(ddrlock_start, 0664, get_ddrlock_start, set_ddrlock_start);
static DEVICE_ATTR(ddrlock_end, 0664, get_ddrlock_end, set_ddrlock_end);

static int rts_ddrlock_probe(struct platform_device *pdev)
{
	struct rts_ddrlock *ddrlock = NULL;
	int err = 0;

	ddrlock = devm_kzalloc(&pdev->dev, sizeof(*ddrlock), GFP_KERNEL);
	if (!ddrlock) {
		err = -ENOMEM;
		dev_err(&pdev->dev, "Couldn't allocate ddrlock object\n");
		goto out;
	}

	ddrlock->dev = get_device(&pdev->dev);

	ddrlock->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!ddrlock->mem) {
		err = -ENOENT;
		dev_err(&pdev->dev, "Failed to get platform mmio memory\n");
		goto out;
	}
	if (!request_mem_region(ddrlock->mem->start,
				resource_size(ddrlock->mem), pdev->name)) {
		err = -EBUSY;
		dev_err(&pdev->dev, "Failed to request mmio memory region\n");
		goto out;
	}
	ddrlock->addr = ioremap_nocache(ddrlock->mem->start,
		resource_size(ddrlock->mem));
	if (!ddrlock->addr) {
		err = -EBUSY;
		dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
		goto err_release_mem;
	}

	platform_set_drvdata(pdev, ddrlock);
	device_create_file(&pdev->dev, &dev_attr_ddrlock_ctrl);
	device_create_file(&pdev->dev, &dev_attr_ddrlock_start);
	device_create_file(&pdev->dev, &dev_attr_ddrlock_end);

	err = sysfs_create_link(pdev->dev.kobj.parent,
			&pdev->dev.kobj, "rts_ddrlock");
	if (err < 0) {
		dev_err(&pdev->dev, "Create sysfs link fail!\n");
		goto err_remove_sys;
	}

	return 0;

err_remove_sys:
	device_remove_file(&pdev->dev, &dev_attr_ddrlock_ctrl);
	device_remove_file(&pdev->dev, &dev_attr_ddrlock_start);
	device_remove_file(&pdev->dev, &dev_attr_ddrlock_end);
err_release_mem:
	release_mem_region(ddrlock->mem->start, resource_size(ddrlock->mem));
out:
	return err;
}

static int rts_ddrlock_remove(struct platform_device *pdev)
{
	struct rts_ddrlock *ddrlock = platform_get_drvdata(pdev);

	sysfs_remove_link(pdev->dev.kobj.parent, "rts_ddrlock");
	device_remove_file(&pdev->dev, &dev_attr_ddrlock_ctrl);
	device_remove_file(&pdev->dev, &dev_attr_ddrlock_start);
	device_remove_file(&pdev->dev, &dev_attr_ddrlock_end);

	release_mem_region(ddrlock->mem->start, resource_size(ddrlock->mem));
	return 0;
}

static const struct of_device_id rts_ddrlock_match[] = {
	{ .compatible = "realtek,rts3915-ddrlock", },
	{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, rts_ddrlock_match);

static struct platform_driver rts_ddrlock_driver = {
	.driver = {
		.name = "rts_ddrlock",
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(rts_ddrlock_match),
	},
	.probe = rts_ddrlock_probe,
	.remove = rts_ddrlock_remove,
};

module_platform_driver(rts_ddrlock_driver);

MODULE_AUTHOR("Wei WANG <wei_wang@realsil.com.cn>");
MODULE_LICENSE("GPL");
