
/* Listing 5.7 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>
#include <linux/pci.h>

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

#define DEVICE_NAME	"parled"

static dev_t parled_dev_number;
static struct class *parled_class;

struct cdev parled_cdev;
struct pardevice *pdev;

struct kobject kobj;

struct parled_attr {
	struct attribute attr;
	ssize_t (*show)(char *);
	ssize_t (*store)(const char *, size_t count);
};

#define glow_show_led(num)	\
	static ssize_t glow_led_##num(const char *buf, size_t count) {	\
		unsigned char bf;							\
		int val;									\
		sscanf(buf, "%d", &val);					\
		parport_claim_or_block(pdev);				\
		bf = parport_read_data(pdev->port);			\
		if (val) {									\
			parport_write_data(pdev->port, bf | (1<<num));	\
		} else {									\
			parport_write_data(pdev->port, bf & ~(1<<num)); \
		}											\
		parport_release(pdev);						\
		return count;								\
	}												\
	static ssize_t show_led_##num(char *buf) {		\
		unsigned char bf;							\
		parport_claim_or_block(pdev);				\
		bf = parport_read_data(pdev->port);			\
		parport_release(pdev);						\
		if ( bf & (1<<num) ) {						\
			return sprintf(buf, "ON\n");			\
		} else {									\
			return sprintf(buf, "OFF\n");			\
		}											\
	}												\
	static struct parled_attr led##num = 				\
		__ATTR(led##num, 0664, show_led_##num, glow_led_##num);

glow_show_led(0);
glow_show_led(1);
glow_show_led(2);
glow_show_led(3);
glow_show_led(4);
glow_show_led(5);
glow_show_led(6);
glow_show_led(7);

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

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

static void parled_detach(struct parport * port)
{
	parport_unregister_device(pdev);
}

static struct parport_driver parled_driver = {
	.name	= DEVICE_NAME,
	.attach	= parled_attach,
	.detach	= parled_detach
};

static ssize_t l_show(struct kobject *kobj, struct attribute *a, char *buf)
{
	struct parled_attr *ledattr = container_of(a, struct parled_attr, attr);

	return ledattr->show ? ledattr->show(buf) : -EIO;
}

static ssize_t l_store(struct kobject *kobj, struct attribute *a,
						const char *buf, size_t count)
{
	struct parled_attr *ledattr = container_of(a, struct parled_attr, attr);

	return ledattr->store ? ledattr->store(buf, count) : -EIO;
}

static struct sysfs_ops sysfs_ops = {
	.show	= l_show,
	.store	= l_store,
};

static struct attribute *led_attrs[] = {
	&led0.attr,
	&led1.attr,
	&led2.attr,
	&led3.attr,
	&led4.attr,
	&led5.attr,
	&led6.attr,
	&led7.attr,
	NULL
};

static struct kobj_type ktype_led = {
	.sysfs_ops	= &sysfs_ops,
	.default_attrs	= led_attrs
};

static int __init parled_init(void)
{
	int err = -ENODEV;
	struct class_device *c_d;
	int parled_major = 0, parled_minor = 0;

	if (parled_major) {
		parled_dev_number = MKDEV(parled_major, parled_minor);
		err = register_chrdev_region(parled_dev_number, 1, DEVICE_NAME);
	} else {
		err = alloc_chrdev_region(&parled_dev_number, 0, 1, DEVICE_NAME);
		parled_major = MAJOR(parled_dev_number);
		parled_minor = MINOR(parled_dev_number);
	}

	if ( err < 0 ) {
		printk(KERN_ERR "%s: Failed to get device number.\n", DEVICE_NAME);
		return err;
	}

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

	c_d = 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);
		err = -EIO;
		goto failure1;
	}

	memset(&kobj, 0, sizeof(kobj));
	kobject_init(&kobj);

	kobj.parent = &c_d->kobj;

	kobject_set_name(&kobj, "control");

	kobj.ktype = &ktype_led;

	if ( (err = kobject_register(&kobj)) ) {
		printk(KERN_ERR "%s: Failed at registering the kobject.\n",
					DEVICE_NAME);
		goto failure0;
	}

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

failure0:
	parport_unregister_driver(&parled_driver);
failure1:
	class_device_destroy(parled_class, parled_dev_number);
	class_destroy(parled_class);
failure2:
	unregister_chrdev_region(parled_dev_number, 1);
	return err;
}

static void __exit parled_cleanup(void)
{
	kobject_unregister(&kobj);
	parport_unregister_driver(&parled_driver);
	class_device_destroy(parled_class, parled_dev_number);
	class_destroy(parled_class);
	unregister_chrdev_region(parled_dev_number, 1);

	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 */
