
/* Listing 5.6 in "Essential Linux Device Drivers"
 * by Sreekrishnan Venkateswaran. */

/* Completed and corrected to produce a full driver
 * by Mats Erik Andersson. */

/* Copyright GPL v2, 2008, 2009,
 * Sreekrishnan Venkateswaran, Mats Erik Andersson. */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/parport.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sreekrishnan Venkateswaran, Mats Erik Andersson");
MODULE_DESCRIPTION("LED driver on parallel port using cdev structure.");

#define DEVICE_NAME	"parled"

static dev_t parled_dev_number;
static struct class *parled_class;

struct cdev parled_cdev;
struct pardevice *pdevp;

static int led_open(struct inode *inodp, struct file *filp)
{
	return 0;
}

static ssize_t led_read(struct file *filp, char __user *buf,
					size_t count, loff_t *ppos)
{
	unsigned char kbuf;

	parport_claim_or_block(pdevp);

	kbuf = parport_read_data(pdevp->port);

	parport_release(pdevp);

	if ( copy_to_user(buf, &kbuf, 1) > 0 )
		return 0;;

	return 1;
} /* led_read */

static ssize_t led_write(struct file *filp, const char __user *buf,
					size_t count, loff_t *ppos)
{
	char kbuf;

	if (copy_from_user(&kbuf, buf, 1))
		return -EFAULT;

	parport_claim_or_block(pdevp);

	parport_write_data(pdevp->port, kbuf);

	parport_release(pdevp);

	return count;
}

static int led_release(struct inode *inodp, struct file *filp)
{
	return 0;
}

static struct file_operations led_fops = {
	.owner		= THIS_MODULE,
	.open		= led_open,
	.read		= led_read,
	.write		= led_write,
	.release	= led_release
};

static int led_preempt(void * handle)
{
	return 1;
}

static void led_attach(struct parport * port)
{
	pdevp = parport_register_device(port, DEVICE_NAME, led_preempt,
									NULL, NULL, 0, NULL);
	if (pdevp == NULL)
		printk(KERN_ERR "%s: Failed to register device with parport.\n",
					DEVICE_NAME);
}

static void led_detach(struct parport * port)
{
	parport_unregister_device(pdevp);
}

static struct parport_driver parled_driver = {
	.name	= DEVICE_NAME,
	.attach	= led_attach,
	.detach	= led_detach
};

static void led_cleanup(void);

static int __init parled_init(void)
{
	int err = -ENODEV;

	if ( alloc_chrdev_region(&parled_dev_number, 0, 1, DEVICE_NAME) < 0 ) {
		printk(KERN_DEBUG "%s: Unable to register device.\n", DEVICE_NAME);
		return -ENODEV;
	}

	parled_class = class_create(THIS_MODULE, DEVICE_NAME);
	if (IS_ERR(parled_class)) {
		printk(KERN_ERR "%s: Failed to create class.\n", DEVICE_NAME);
		goto failure2;
	}

	cdev_init(&parled_cdev, &led_fops);
	parled_cdev.owner = THIS_MODULE;

	if (cdev_add(&parled_cdev, parled_dev_number, 1)) {
		printk(KERN_ERR "%s: Failed in adding cdev entity.\n", DEVICE_NAME);
		goto failure1;
	}

	class_device_create(parled_class, NULL, parled_dev_number, NULL, DEVICE_NAME);

	if (parport_register_driver(&parled_driver)) {
		printk(KERN_ERR "%s: Failed to register with parport.\n",
					DEVICE_NAME);
		led_cleanup();
		return -EIO;
	}

	printk(KERN_INFO "%s: LED driver initialized.\n", DEVICE_NAME);
	return 0;

failure1:
	class_destroy(parled_class);
failure2:
	unregister_chrdev_region(MAJOR(parled_dev_number), 1);
	return err;
}

static void __exit led_cleanup(void)
{
	class_device_destroy(parled_class, MKDEV(MAJOR(parled_dev_number), 0));
	cdev_del(&parled_cdev);
	class_destroy(parled_class);
	unregister_chrdev_region(parled_dev_number, 1);
	return;
}

static void __exit parled_cleanup(void)
{
	parport_unregister_driver(&parled_driver);
	led_cleanup();

	printk(KERN_INFO "%s: LED driver removed.\n", DEVICE_NAME);
	return;
}

module_init(parled_init);
module_exit(parled_cleanup);

/* vi: set ai sw=4 ts=4 */
