// SPDX-License-Identifier: GPL-2.0
/*
 * drhyme_sensor.c - serious-but-small Linux platform driver example.
 *
 * The driver demonstrates:
 *   - module loading and platform_driver registration
 *   - probe/remove lifecycle
 *   - per-device state
 *   - misc character device creation
 *   - read/open/release file operations
 *   - ioctl ABI using _IO/_IOR/_IOW
 *   - Device Tree and platform modalias metadata
 *
 * For lab use, create_demo_device=1 registers a synthetic platform_device
 * from this module. Real hardware normally appears from Device Tree, ACPI,
 * PCI, USB, or board/platform code instead.
 */

#include <linux/atomic.h>
#include <linux/compat.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#include "drhyme_sensor_uapi.h"

#define DRHYME_DRIVER_NAME "drhyme-sensor"
#define DRHYME_ABI_VERSION 1

struct drhyme_sensor {
	struct device *dev;
	struct miscdevice miscdev;
	struct mutex lock;
	atomic64_t read_count;
	u32 config;
	bool enabled;
	char misc_name[32];
};

static bool create_demo_device = true;
module_param(create_demo_device, bool, 0444);
MODULE_PARM_DESC(create_demo_device,
		 "register a synthetic platform_device for lab testing");

static struct platform_device *demo_pdev;

static int drhyme_open(struct inode *inode, struct file *file)
{
	struct miscdevice *miscdev = file->private_data;
	struct drhyme_sensor *sensor;

	sensor = container_of(miscdev, struct drhyme_sensor, miscdev);
	file->private_data = sensor;

	dev_dbg(sensor->dev, "open\n");
	return 0;
}

static int drhyme_release(struct inode *inode, struct file *file)
{
	struct drhyme_sensor *sensor = file->private_data;

	dev_dbg(sensor->dev, "release\n");
	return 0;
}

static ssize_t drhyme_read(struct file *file, char __user *buf,
			   size_t count, loff_t *ppos)
{
	struct drhyme_sensor *sensor = file->private_data;
	char tmp[96];
	int len;
	u32 config;
	bool enabled;
	u64 reads;

	mutex_lock(&sensor->lock);
	config = sensor->config;
	enabled = sensor->enabled;
	reads = atomic64_inc_return(&sensor->read_count);
	mutex_unlock(&sensor->lock);

	len = scnprintf(tmp, sizeof(tmp),
			"enabled=%u config=%u read_count=%llu\n",
			enabled, config, reads);

	return simple_read_from_buffer(buf, count, ppos, tmp, len);
}

static long drhyme_ioctl(struct file *file, unsigned int cmd,
			 unsigned long arg)
{
	struct drhyme_sensor *sensor = file->private_data;
	struct drhyme_sensor_status status;
	struct drhyme_sensor_config config;

	if (_IOC_TYPE(cmd) != DRHYME_IOCTL_MAGIC)
		return -ENOTTY;

	switch (cmd) {
	case DRHYME_IOCTL_RESET:
		mutex_lock(&sensor->lock);
		sensor->enabled = false;
		sensor->config = 0;
		atomic64_set(&sensor->read_count, 0);
		mutex_unlock(&sensor->lock);
		return 0;

	case DRHYME_IOCTL_GET_STATUS:
		memset(&status, 0, sizeof(status));

		mutex_lock(&sensor->lock);
		status.abi_version = DRHYME_ABI_VERSION;
		status.enabled = sensor->enabled;
		status.config = sensor->config;
		status.read_count = atomic64_read(&sensor->read_count);
		mutex_unlock(&sensor->lock);

		if (copy_to_user((void __user *)arg, &status, sizeof(status)))
			return -EFAULT;

		return 0;

	case DRHYME_IOCTL_SET_CONFIG:
		if (copy_from_user(&config, (void __user *)arg,
				   sizeof(config)))
			return -EFAULT;

		mutex_lock(&sensor->lock);
		sensor->config = config.config;
		sensor->enabled = true;
		mutex_unlock(&sensor->lock);
		return 0;

	default:
		return -ENOTTY;
	}
}

static const struct file_operations drhyme_fops = {
	.owner = THIS_MODULE,
	.open = drhyme_open,
	.read = drhyme_read,
	.unlocked_ioctl = drhyme_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = compat_ptr_ioctl,
#endif
	.release = drhyme_release,
	.llseek = no_llseek,
};

static int drhyme_probe(struct platform_device *pdev)
{
	struct drhyme_sensor *sensor;
	u32 default_config = 0;
	int ret;

	sensor = devm_kzalloc(&pdev->dev, sizeof(*sensor), GFP_KERNEL);
	if (!sensor)
		return -ENOMEM;

	sensor->dev = &pdev->dev;
	mutex_init(&sensor->lock);
	atomic64_set(&sensor->read_count, 0);

	device_property_read_u32(&pdev->dev, "digital-rhyme,default-config",
				 &default_config);
	sensor->config = default_config;
	sensor->enabled = default_config != 0;

	snprintf(sensor->misc_name, sizeof(sensor->misc_name),
		 "drhyme_sensor%d", pdev->id < 0 ? 0 : pdev->id);

	sensor->miscdev.minor = MISC_DYNAMIC_MINOR;
	sensor->miscdev.name = sensor->misc_name;
	sensor->miscdev.fops = &drhyme_fops;
	sensor->miscdev.parent = &pdev->dev;

	ret = misc_register(&sensor->miscdev);
	if (ret)
		return dev_err_probe(&pdev->dev, ret,
				     "failed to register misc device\n");

	platform_set_drvdata(pdev, sensor);

	dev_info(&pdev->dev, "probed; userspace node is /dev/%s\n",
		 sensor->misc_name);
	return 0;
}

static int drhyme_remove(struct platform_device *pdev)
{
	struct drhyme_sensor *sensor = platform_get_drvdata(pdev);

	misc_deregister(&sensor->miscdev);
	dev_info(&pdev->dev, "removed\n");
	return 0;
}

static const struct of_device_id drhyme_of_match[] = {
	{ .compatible = "digital-rhyme,drhyme-sensor" },
	{ }
};
MODULE_DEVICE_TABLE(of, drhyme_of_match);

static const struct platform_device_id drhyme_platform_ids[] = {
	{ .name = DRHYME_DRIVER_NAME },
	{ }
};
MODULE_DEVICE_TABLE(platform, drhyme_platform_ids);

static struct platform_driver drhyme_driver = {
	.probe = drhyme_probe,
	.remove = drhyme_remove,
	.id_table = drhyme_platform_ids,
	.driver = {
		.name = DRHYME_DRIVER_NAME,
		.of_match_table = drhyme_of_match,
	},
};

static int __init drhyme_init(void)
{
	int ret;

	ret = platform_driver_register(&drhyme_driver);
	if (ret)
		return ret;

	if (create_demo_device) {
		demo_pdev = platform_device_register_simple(DRHYME_DRIVER_NAME,
							    PLATFORM_DEVID_AUTO,
							    NULL, 0);
		if (IS_ERR(demo_pdev)) {
			ret = PTR_ERR(demo_pdev);
			platform_driver_unregister(&drhyme_driver);
			return ret;
		}
	}

	pr_info(DRHYME_DRIVER_NAME ": module loaded\n");
	return 0;
}

static void __exit drhyme_exit(void)
{
	if (demo_pdev)
		platform_device_unregister(demo_pdev);

	platform_driver_unregister(&drhyme_driver);
	pr_info(DRHYME_DRIVER_NAME ": module unloaded\n");
}

module_init(drhyme_init);
module_exit(drhyme_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Digital Rhyme Knowledge Base");
MODULE_DESCRIPTION("Platform driver example with miscdevice and ioctl ABI");
