// SPDX-License-Identifier: (GPL-2.0+ OR MIT)

/*
 * drivers/amlogic/media/camera/common/flashlight.c
 *
 * Copyright (C) 2017 Amlogic, 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 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.
 *
 */

#include <linux/cdev.h>
#include <linux/types.h>

#include <linux/fs.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/amlogic/media/camera/flashlight.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/err.h>

#define FLASHLIGHT_MODULE_NAME   "flashlight"
#define FLASHLIGHT_DRIVER_NAME "flashlight"
#define FLASHLIGHT_DEVICE_NAME   "flashlight"
#define FLASHLIGHT_CLASS_NAME   "flashlight"

static dev_t flashlight_devno;
static struct cdev *flashlight_cdev;
static struct device *devp;

static enum aml_plat_flashlight_status_s flashlight_flag = FLASHLIGHT_OFF;

static ssize_t flashlight_ctrl_store(struct class *cla,
			       struct class_attribute *attr,
			       const char *buf, size_t count);
static ssize_t flashlightflag_show(struct class *cla,
				  struct class_attribute *attr, char *buf);
static ssize_t flashlight_ctrl_flag_store(struct class *cla,
				  struct class_attribute *attr,
				  const char *buf, size_t count);
static int flashlight_open(struct inode *inode, struct file *file);
static int flashlight_release(struct inode *inode, struct file *file);
static int flashlight_probe(struct platform_device *pdev);
static int flashlight_remove(struct platform_device *pdev);

static struct platform_driver flashlight_driver = {
	.probe = flashlight_probe, .remove = flashlight_remove, .driver = {
		.name =
		FLASHLIGHT_DRIVER_NAME, .owner = THIS_MODULE,
	},
};

static const struct file_operations flashlight_fops = {
	.open = flashlight_open, .release = flashlight_release,
};

static CLASS_ATTR_WO(flashlight_ctrl);
static CLASS_ATTR_WO(flashlight_ctrl_flag);
static CLASS_ATTR_RO(flashlightflag);

static struct attribute *flashlight_class_attrs[] = {
	&class_attr_flashlight_ctrl.attr,
	&class_attr_flashlight_ctrl_flag.attr,
	&class_attr_flashlightflag.attr,
	NULL,
};
ATTRIBUTE_GROUPS(flashlight_class);

static struct class flashlight_class = {
		.name = FLASHLIGHT_CLASS_NAME,
		.owner = THIS_MODULE,
		.class_groups = flashlight_class_groups
	};

static ssize_t flashlight_ctrl_store(struct class *cla,
			       struct class_attribute *attr,
			       const char *buf, size_t count)
{
	struct aml_plat_flashlight_data_s *pdata = NULL;
	struct device *dev = NULL;

	dev = devp;
	pdata = (struct aml_plat_flashlight_data_s *)dev->platform_data;
	if (!pdata) {
		pr_err("%s platform data is required!\n", __func__);
		return -1;
	}
	if (!strncmp(buf, "0", 1)) {
		if (pdata->flashlight_off)
			pdata->flashlight_off();
	} else if (!strncmp(buf, "1", 1)) {
		if (pdata->flashlight_on)
			pdata->flashlight_on();
	} else {
		pr_err("%s:%s error!Not support this parameter\n",
		       FLASHLIGHT_MODULE_NAME, __func__);
		return -EINVAL;
	}
	return count;
}

static ssize_t flashlightflag_show(struct class *cla,
				  struct class_attribute *attr, char *buf)
{
	sprintf(buf, "%d", (int)flashlight_flag);
	return strlen(buf);
}

static ssize_t flashlight_ctrl_flag_store(struct class *cla,
				  struct class_attribute *attr,
				  const char *buf, size_t count)
{
	if (!strlen(buf))
		pr_info("%s parameter is required!\n", __func__);
	flashlight_flag = (enum aml_plat_flashlight_status_s)(buf[0] - '0');
	return count;
}

static int flashlight_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int flashlight_release(struct inode *inode, struct file *file)
{
	return 0;
}

static int flashlight_probe(struct platform_device *pdev)
{
	int ret;
	struct aml_plat_flashlight_data_s *pdata = NULL;

	ret = alloc_chrdev_region(&flashlight_devno, 0,
			1, FLASHLIGHT_DRIVER_NAME);
	if (ret < 0) {
		pr_err("%s:%s failed to allocate major number\n",
		       FLASHLIGHT_MODULE_NAME, __func__);
		ret = -ENODEV;
		goto out;
	}
	ret = class_register(&flashlight_class);
	if (ret < 0) {
		pr_err("%s:%s  failed to register class\n",
			FLASHLIGHT_MODULE_NAME, __func__);
		goto error1;
	}
	flashlight_cdev = cdev_alloc();
	if (!flashlight_cdev) {
		pr_err("%s:%s: failed to allocate memory\n",
			FLASHLIGHT_MODULE_NAME, __func__);
		ret = -ENOMEM;
		goto error2;
	}
	cdev_init(flashlight_cdev, &flashlight_fops);
	flashlight_cdev->owner = THIS_MODULE;
	ret = cdev_add(flashlight_cdev, flashlight_devno, 1);
	if (ret) {
		pr_err("%s:%s: failed to add device\n",
			FLASHLIGHT_MODULE_NAME, __func__);
		goto error3;
	}
	devp = device_create(&flashlight_class, NULL, flashlight_devno, NULL,
			     FLASHLIGHT_DEVICE_NAME);
	if (IS_ERR(devp)) {
		pr_err("%s:%s failed to create device node\n",
			FLASHLIGHT_MODULE_NAME, __func__);
		ret = PTR_ERR(devp);
		goto error3;
	}
	pr_info("%s:%s device %s created\n", FLASHLIGHT_MODULE_NAME,
	       __func__, FLASHLIGHT_DEVICE_NAME);
	pdata = pdev->dev.platform_data;
	if (!pdata) {
		dev_err(&pdev->dev, "platform data is required!\n");
		ret = -EINVAL;
		goto error4;
	}
	devp->platform_data = pdata;
	return 0;
error4:
	device_destroy(NULL, flashlight_devno);
error3:
	cdev_del(flashlight_cdev);
error2:
	class_unregister(&flashlight_class);
error1:
	unregister_chrdev_region(flashlight_devno, 1);
out:
	return ret;
}

static int flashlight_remove(struct platform_device *pdev)
{
	unregister_chrdev_region(flashlight_devno, 1);
	class_unregister(&flashlight_class);
	device_destroy(NULL, flashlight_devno);
	cdev_del(flashlight_cdev);
	return 0;
}

int set_flashlight(bool mode)
{
	struct aml_plat_flashlight_data_s *pdata = NULL;

	if (devp && devp->platform_data) {
		pdata = devp->platform_data;
		if (!mode) {
			if (pdata->flashlight_off)
				pdata->flashlight_off();
		} else {
			if (pdata->flashlight_on)
				pdata->flashlight_on();
		}
	}
	return 0;
}
EXPORT_SYMBOL(set_flashlight);

enum aml_plat_flashlight_status_s get_flashlightflag(void)
{
	return flashlight_flag;
}
EXPORT_SYMBOL(get_flashlightflag);

int __init flashlight_init(void)
{
	int ret = -1;

	ret = platform_driver_register(&flashlight_driver);
	if (ret != 0) {
		pr_err("failed to register flashlight driver,error %d\n", ret);
		return -ENODEV;
	}
	return ret;
}

void __exit flashlight_exit(void)
{
	platform_driver_unregister(&flashlight_driver);
}

//module_init(flashlight_init);
//module_exit(flashlight_exit);

//MODULE_DESCRIPTION("AMLOGIC flashlight driver");
//MODULE_LICENSE("GPL");
//MODULE_AUTHOR("amlogic");
