Subject: [PATCH 3/3] support for ks8995m managed switch on the SPI bus

From: Grant Likely <[email-masked]>
Date: Sat, 23 Jul 2005 10:17:27 -0400

Add support for reading and writing Micrel ks8995m managed switch
register set from user space over the SPI bus.

Signed-off-by: Grant Likely <[email-masked]>

diff -ruNp linux-spi-mpc5200/drivers/spi/Kconfig
linux-spi-mpc5200-ks8995m/drivers/spi/Kconfig
--- linux-spi-mpc5200/drivers/spi/Kconfig	2005-07-22 17:16:13.000000000 -0400
+++ linux-spi-mpc5200-ks8995m/drivers/spi/Kconfig	2005-07-23
02:59:02.000000000 -0400
@@ -30,5 +30,12 @@ config SPI_BUS_MPC52XX_PSC
 comment "SPI slaves"
 	depends on SPI
 
+config KS8995M
+	tristate "Micrel/Kendin KS8995M Ethernet Switch SPI interface driver"
+	depends on SPI
+	help
+	  Say Y here if you need to control a Kendin/Micrel KS8995M managed
+	  Ethernet switch via the SPI interface.
+
 endmenu
 
diff -ruNp linux-spi-mpc5200/drivers/spi/Makefile
linux-spi-mpc5200-ks8995m/drivers/spi/Makefile
--- linux-spi-mpc5200/drivers/spi/Makefile	2005-07-22 17:16:13.000000000 -0400
+++ linux-spi-mpc5200-ks8995m/drivers/spi/Makefile	2005-07-23
02:59:02.000000000 -0400
@@ -4,3 +4,4 @@
 
 obj-$(CONFIG_SPI)			+= spi-core.o
 obj-$(CONFIG_SPI_BUS_MPC52XX_PSC)	+= spi-mpc52xx-psc.o
+obj-$(CONFIG_KS8995M)			+= ks8995m.o
diff -ruNp linux-spi-mpc5200/drivers/spi/ks8995m.c
linux-spi-mpc5200-ks8995m/drivers/spi/ks8995m.c
--- linux-spi-mpc5200/drivers/spi/ks8995m.c	1969-12-31 19:00:00.000000000 -0500
+++ linux-spi-mpc5200-ks8995m/drivers/spi/ks8995m.c	2005-07-23
03:10:37.000000000 -0400
@@ -0,0 +1,237 @@
+/*
+ * drivers/spi/ks8995m-spi.c
+ *
+ * Micrel/Kendin KS8995M managed switch SPI interface driver
+ *
+ * Maintainer : Grant Likely <[email-masked]>
+ *
+ * This file is in the public domain
+ */
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/interrupt.h>
+#include <linux/spi.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+
+#define KS8995M_NUM_REGS  (128)
+
+struct ks8995m_data {
+	struct cdev cdev;
+	struct spi_device *spi_dev;
+	uint8_t in_buff[KS8995M_NUM_REGS+2];
+	uint8_t out_buff[KS8995M_NUM_REGS+2];
+};
+
+static int ks8995m_major;
+static int ks8995m_minor;
+static int ks8995m_minor_offset = 0;
+
+/* Forward method declarations for cdev */
+static int ks8995m_open(struct inode *inode, struct file *filp);
+static ssize_t ks8995m_read(struct file *filp, char __user *buff,
+				size_t count, loff_t *offp);
+static ssize_t ks8995m_write(struct file *filp, const char __user *buff,
+				size_t count, loff_t *offp);
+static int ks8995m_release(struct inode *inode, struct file *filp);
+
+static struct file_operations ks8995m_fops = {
+	.owner = THIS_MODULE,
+	.open = ks8995m_open,
+	.read = ks8995m_read,
+	.write = ks8995m_write,
+	.release = ks8995m_release,
+};
+
+static int ks8995m_open(struct inode *inode, struct file *filp)
+{
+	struct ks8995m_data *data = container_of(inode->i_cdev, struct
ks8995m_data, cdev);
+
+	/* Private data is always set to the ks8995m_data structure */
+	filp->private_data = data;
+	return 0;
+}
+
+static ssize_t ks8995m_check_size(int start, int count)
+{
+	if ((count < 1) || (start > KS8995M_NUM_REGS))
+		return 0;
+
+	if (start + count > KS8995M_NUM_REGS)
+		count = KS8995M_NUM_REGS - start;
+
+	return count;
+}
+
+static ssize_t ks8995m_read(struct file *filp, char __user *buff,
+				size_t count, loff_t *offp)
+{
+	struct ks8995m_data *data = filp->private_data;
+	struct spi_bus *bus;
+	int ret;
+
+	count = ks8995m_check_size(*offp, count);
+	if (!count)
+		return 0;
+
+	data->out_buff[0] = 0x03; /* read command */
+	data->out_buff[1] = *offp; /* address */
+	memset(&data->out_buff[2], 0, count);
+
+	bus = to_spi_bus(data->spi_dev->dev.parent);
+
+	ret = bus->ops->transfer(bus, data->spi_dev->id, SPI_CLKEDGE_RISING,
+				data->in_buff, data->out_buff, count+2);
+	if (ret)
+		return ret;
+
+	copy_to_user(buff, &data->in_buff[2], count);
+	*offp += count;
+
+	return count;
+}
+
+static ssize_t ks8995m_write(struct file *filp, const char __user *buff,
+				size_t count, loff_t *offp)
+{
+	struct ks8995m_data *data = filp->private_data;
+	struct spi_bus *bus;
+	int ret;
+
+	count = ks8995m_check_size(*offp, count);
+	if (!count)
+		return 0;
+
+	data->out_buff[0] = 0x02; /* write command */
+	data->out_buff[1] = *offp; /* address */
+	copy_from_user(&data->out_buff[2], buff, count);
+
+	bus = to_spi_bus(data->spi_dev->dev.parent);
+
+	ret = bus->ops->transfer(bus, data->spi_dev->id, SPI_CLKEDGE_RISING,
+				data->in_buff, data->out_buff, count+2);
+	if (ret)
+		return ret;
+
+	*offp += count;
+
+	return count;
+}
+
+static int ks8995m_release(struct inode *inode, struct file *filp)
+{
+	return 0;
+}
+
+static int ks8995m_probe (struct device *dev)
+{
+	struct ks8995m_data *data;
+	struct spi_device *spi_dev;
+	int devno, err;
+
+	spi_dev = to_spi_dev(dev);
+	printk("ks8995m_probe(dev->bus_id=%s) (name=%s)\n",
+		dev->bus_id, spi_dev->name);
+
+	data = kmalloc(sizeof(struct ks8995m_data), GFP_KERNEL);
+	if (!data)
+	{
+		printk(KERN_ALERT "Could not allocate driver memory\n");
+		return -ENOMEM;
+	}
+	memset(data, 0, sizeof(struct ks8995m_data));
+
+	dev->driver_data = data;
+
+	data->spi_dev = spi_dev;
+	cdev_init(&data->cdev, &ks8995m_fops);
+	data->cdev.owner = THIS_MODULE;
+	data->cdev.ops = &ks8995m_fops;
+
+	devno = MKDEV(ks8995m_major, ks8995m_minor + ks8995m_minor_offset++);
+	err = cdev_add(&data->cdev, devno, 1);
+	if (err)
+	{
+		printk(KERN_INFO "ks8995m: could not register char dev\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int ks8995m_remove (struct device *dev)
+{
+	struct ks8995m_data *data = dev->driver_data;
+
+	printk("ks8995_spi_remove(dev->bus_id=%s)\n", dev->bus_id);
+
+	cdev_del(&data->cdev);
+	kfree(data);
+	dev->driver_data = NULL;
+
+	return 0;
+}
+
+static struct spi_driver ks8995m_driver = {
+	.name = "ks8995m",
+	.module = THIS_MODULE,
+	.driver = {
+		.probe = ks8995m_probe,
+		.remove = ks8995m_remove,
+	},
+};
+
+static int __init ks8995m_init(void)
+{
+	dev_t dev;
+	int ret;
+
+	printk(KERN_ALERT "ks8995m: initializing\n");
+
+	if (alloc_chrdev_region(&dev, 0, 128, "ks8995m"))
+	{
+		printk(KERN_ALERT "ks8995m: could not allocate chrdev\n");
+		goto fail_alloc_chrdev;
+	}
+	ks8995m_major = MAJOR(dev);
+	ks8995m_minor = MINOR(dev);
+
+	printk(KERN_ALERT "ks8995m: allocated chrdev region (%i:%i)!\n",
+	       ks8995m_major, ks8995m_minor);
+
+	ret = spi_driver_register(&ks8995m_driver);
+	if (ret)
+	{
+		printk(KERN_ALERT "ks8995m: could not register driver\n");
+		goto fail_register;
+	}
+
+
+	return 0;
+
+fail_register:
+
+fail_alloc_chrdev:
+	return -1;
+}
+
+static void __exit ks8995m_exit(void)
+{
+	int devno = MKDEV(ks8995m_major, ks8995m_minor);
+
+	spi_driver_unregister(&ks8995m_driver);
+	unregister_chrdev_region(devno, 128);
+
+	printk(KERN_ALERT "ks8995m: exiting\n");
+}
+
+module_init(ks8995m_init);
+module_exit(ks8995m_exit);
+
+MODULE_AUTHOR("Grant Likely <[email-masked]>");
+MODULE_DESCRIPTION("SPI register interface for ks8995m managed switch");
+MODULE_LICENSE("Dual BSD/GPL");