Signed-off-by: Prashant P Shah <pshah.mumbai@xxxxxxxxx> --- Makefile | 24 +++ si700x.c | 504 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ si700x.h | 102 +++++++++++++ 3 files changed, 630 insertions(+), 0 deletions(-) create mode 100644 Makefile create mode 100644 si700x.c create mode 100644 si700x.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9b33a3b --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +obj-m := si700x.o + +KERNELDIR := /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +default: + $(MAKE) -C $(KERNELDIR) M=$(PWD) + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers + +re: + sudo rmmod si700x + make clean + $(MAKE) -C $(KERNELDIR) M=$(PWD) + sudo insmod ./si700x.ko + sudo ./test + +depend .depend dep: + $(CC) $(CFLAGS) -M *.c > .depend + +ifeq (.depend,$(wildcard .depend)) + include .depend +endif diff --git a/si700x.c b/si700x.c new file mode 100644 index 0000000..92cd47d --- /dev/null +++ b/si700x.c @@ -0,0 +1,504 @@ +/* +* Copyright (C) 2012 Prashant Shah, pshah.mumbai@xxxxxxxxx +* Copyright (C) 2012 Silicon Labs, Inc. (www.silabs.com) +* +* 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. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + * Silicon Labs Si700x USB Evaluation Board driver. + * http://www.silabs.com + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/ioctl.h> +#include <linux/mutex.h> + +#include "si700x.h" + +/* Usb transfer request */ +struct transfer_req { + u8 type; + u8 status; + u8 address; + u8 length; + u8 data[4]; +} __attribute__ ((__packed__)); + +struct si700x_dev { + struct usb_device *udev; /* the usb device */ + struct usb_interface *interface; /* the usb interface */ + struct transfer_req buffer; + int buffer_size; + int buffer_status; + struct mutex lock; +}; +#define to_dev(d) container_of(d, struct si700x_dev, kref) + +static struct usb_driver si700x_driver; + +static int si700x_open(struct inode *i, struct file *f) +{ + struct si700x_dev *dev; + struct usb_interface *interface; + int minor; + + pr_debug("Si700x: %s\n", __func__); + + minor = iminor(i); + + interface = usb_find_interface(&si700x_driver, minor); + if (!interface) { + printk(KERN_ERR "Si700x: failed to find interface " + "for minor %d", minor); + return -ENODEV; + } + + dev = usb_get_intfdata(interface); + if (!dev) { + printk(KERN_ERR "Si700x: failed to find device " + "for minor %d\n", minor); + return -ENODEV; + } + + /* save our object in the file's private structure */ + mutex_lock(&dev->lock); + f->private_data = dev; + mutex_unlock(&dev->lock); + return 0; +} + +static int si700x_release(struct inode *i, struct file *f) +{ + struct si700x_dev *dev; + + pr_debug("Si700x: %s\n", __func__); + + dev = (struct si700x_dev *)f->private_data; + if (dev == NULL) { + printk(KERN_ERR "Si700x: failed to find device from interface\n"); + return -ENODEV; + } + mutex_lock(&dev->lock); + f->private_data = NULL; + mutex_unlock(&dev->lock); + return 0; +} + +/* + * USB read function reads from the device with the address of the + * si700x_dev.buffer which was filled by the USB write function previously. + */ +static ssize_t si700x_read(struct file *f, char __user *user_buffer, + size_t count, loff_t *ppos) +{ + struct si700x_dev *dev; + int retval = 0; + int actual_length = 0; + + pr_debug("Si700x: %s\n", __func__); + + dev = (struct si700x_dev *)f->private_data; + + /* check the size of the data buffer */ + if (count != dev->buffer_size) { + printk(KERN_ERR "Si700x: invalid buffer size, " + "it should be %d bytes\n", dev->buffer_size); + return -EFAULT; + } + + /* check buffer status of the previous write function */ + if (dev->buffer_status != 1) { + printk(KERN_ERR "Si700x: previous URB write was not successfull\n"); + return -EFAULT; + } + + /* check access to user space buffer */ + if (!access_ok(VERIFY_WRITE, user_buffer, dev->buffer_size)) { + printk(KERN_ERR "Si700x: invalid user space data\n"); + return -EFAULT; + } + + mutex_lock(&dev->lock); + dev->buffer_status = 0; + + retval = usb_bulk_msg(dev->udev, + usb_rcvbulkpipe(dev->udev, PIPE_DATA_IN), + &dev->buffer, dev->buffer_size, /* buffer, buffer length */ + &actual_length, 0); /* bytes written, interval */ + if (retval < 0) { + printk(KERN_ERR "Si700x: failed to read URB\n"); + mutex_unlock(&dev->lock); + return retval; + } + + /* check if the read status is ok */ + if (dev->buffer.status != XFER_STATUS_SUCCESS) { + printk(KERN_ERR "Si700x: device returned error " + "status number %d\n", dev->buffer.status); + mutex_unlock(&dev->lock); + return -EFAULT; + } + + /* saving the original data in the buffer for the USB read function */ + if (copy_to_user(user_buffer, &dev->buffer, dev->buffer_size)) { + printk(KERN_ERR "Si700x: failed to copy data to user space\n"); + mutex_unlock(&dev->lock); + return -EFAULT; + } + + dev->buffer_status = 1; + mutex_unlock(&dev->lock); + + return actual_length; +} + +/* + * USB write function writes to the device and store the written data in the + * si700x_dev.buffer which is used by the USB read function + */ +static ssize_t si700x_write(struct file *f, const char __user *user_buffer, + size_t count, loff_t *ppos) +{ + struct si700x_dev *dev; + int retval = 0; + int actual_length = 0; + + pr_debug("Si700x: %s\n", __func__); + + dev = (struct si700x_dev *)f->private_data; + + /* check the size of the data buffer */ + if (count != dev->buffer_size) { + printk(KERN_ERR "Si700x: invalid buffer size, " + "it should be %d bytes\n", dev->buffer_size); + return -EFAULT; + } + + /* check access to user space buffer */ + if (!access_ok(VERIFY_READ, user_buffer, dev->buffer_size)) { + printk(KERN_ERR "Si700x: invalid user space data\n"); + return -EFAULT; + } + + mutex_lock(&dev->lock); + /* saving the original data in the buffer for the USB read function */ + if (copy_from_user(&dev->buffer, user_buffer, dev->buffer_size)) { + printk(KERN_ERR "Si700x: failed to copy data from user space\n"); + mutex_unlock(&dev->lock); + return -EFAULT; + } + + dev->buffer_status = 0; + + retval = usb_bulk_msg(dev->udev, + usb_sndbulkpipe(dev->udev, PIPE_DATA_OUT), + &dev->buffer, dev->buffer_size, /* buffer, buffer length */ + &actual_length, 0); /* bytes written, interval */ + if (retval < 0) { + printk(KERN_ERR "Si700x: failed to write URB\n"); + mutex_unlock(&dev->lock); + return retval; + } + + dev->buffer_status = 1; + mutex_unlock(&dev->lock); + + return actual_length; +} + +static long si700x_ioctl(struct file *f, unsigned int cmd, unsigned long arg) +{ + struct si700x_dev *dev; + int retval = 0; + u16 version = 0; + u8 port_count = 0; + u8 board_id = 0; + u16 port_id = 0; + + pr_debug("Si700x: %s\n", __func__); + + dev = (struct si700x_dev *)f->private_data; + + /* check if the ioctl is for the right device and within range */ + if (_IOC_NR(cmd) > SI700X_IOC_MAXNR) + return -ENOTTY; + + /* check if the data read and write from user is allowed */ + if (_IOC_DIR(cmd) & _IOC_READ) + retval = !access_ok(VERIFY_WRITE, (void __user *)arg, + _IOC_SIZE(cmd)); + else if (_IOC_DIR(cmd) & _IOC_WRITE) + retval = !access_ok(VERIFY_READ, (void __user *)arg, + _IOC_SIZE(cmd)); + if (retval) + return -EFAULT; + + mutex_lock(&dev->lock); + switch (cmd) { + + case SI700X_LED_ON: + /* turn on the LED */ + retval = usb_control_msg(dev->udev, + usb_sndctrlpipe(dev->udev, 0), + REQ_SET_LED, CMD_VEN_DEV_OUT, + 1, 0, /* value, index */ + NULL, 0, 0); /* data, size, timeout */ + if (retval < 0) { + printk(KERN_ERR "Si700x: failed to turn ON the LED\n"); + goto error; + } + mutex_unlock(&dev->lock); + return 0; + + case SI700X_LED_OFF: + /* turn off the LED */ + retval = usb_control_msg(dev->udev, + usb_sndctrlpipe(dev->udev, 0), + REQ_SET_LED, CMD_VEN_DEV_OUT, + 0, 0, /* value, index */ + NULL, 0, 0); /* data, size, timeout */ + if (retval < 0) { + printk(KERN_ERR "Si700x: failed to turn OFF the LED\n"); + goto error; + } + mutex_unlock(&dev->lock); + return 0; + + case SI700X_VERSION: + /* read vesion number from the board */ + retval = usb_control_msg(dev->udev, + usb_rcvctrlpipe(dev->udev, 0), + REQ_GET_VERSION, CMD_VEN_DEV_IN, + 0, 0, /* value, index */ + &version, 2, 0); /* data, size, timeout */ + if (retval < 0) { + printk(KERN_ERR "Si700x: failed to read version number\n"); + goto error; + } + mutex_unlock(&dev->lock); + return __put_user(version, (u16 __user *)arg); + + case SI700X_PORT_COUNT: + /* read port count from the board */ + retval = usb_control_msg(dev->udev, + usb_rcvctrlpipe(dev->udev, 0), + REQ_GET_PORT_COUNT, CMD_VEN_DEV_IN, + 0, 0, /* value, index */ + &port_count, 1, 0); /* data, size, timeout */ + if (retval < 0) { + printk(KERN_ERR "Si700x: failed to read port count\n"); + goto error; + } + mutex_unlock(&dev->lock); + return __put_user(port_count, (u8 __user *)arg); + + case SI700X_BOARDID: + /* read board id from the board */ + retval = usb_control_msg(dev->udev, + usb_rcvctrlpipe(dev->udev, 0), + REQ_GET_BOARD_ID, CMD_VEN_DEV_IN, + 0, 0, /* value, index */ + &board_id, 1, 0); /* data, size, timeout */ + if (retval < 0) { + printk(KERN_ERR "Si700x: failed to read board id\n"); + goto error; + } + mutex_unlock(&dev->lock); + return __put_user(board_id, (u8 __user *)arg); + + case SI700X_SETPROG_ON: + /* turn on programming */ + retval = usb_control_msg(dev->udev, + usb_sndctrlpipe(dev->udev, 0), + REQ_SET_PROG, CMD_VEN_DEV_OUT, + 1, 0, /* value, index - port */ + NULL, 0, 0); /* data, size, timeout */ + if (retval < 0) { + printk(KERN_ERR "Si700x: failed to turn ON the programming mode\n"); + goto error; + } + mutex_unlock(&dev->lock); + return 0; + + case SI700X_SETPROG_OFF: + /* turn off programming */ + retval = usb_control_msg(dev->udev, + usb_sndctrlpipe(dev->udev, 0), + REQ_SET_PROG, CMD_VEN_DEV_OUT, + 0, 0, /* value, index - port */ + NULL, 0, 0); /* data, size, timeout */ + if (retval < 0) { + printk(KERN_ERR "Si700x: failed to turn OFF the programming mode\n"); + goto error; + } + mutex_unlock(&dev->lock); + return 0; + + case SI700X_SETSLEEP_ON: + port_id = arg; + /* turn on sleeping */ + retval = usb_control_msg(dev->udev, + usb_sndctrlpipe(dev->udev, 0), + REQ_SET_SLEEP, CMD_VEN_DEV_OUT, + 0, port_id, /* value, index - port */ + NULL, 0, 0); /* data, size, timeout */ + if (retval < 0) { + printk(KERN_ERR "Si700x: failed to turn ON the sleeping " + "for port %d\n", port_id); + goto error; + } + mutex_unlock(&dev->lock); + return 0; + + case SI700X_SETSLEEP_OFF: + port_id = arg; + /* turn off sleeping */ + retval = usb_control_msg(dev->udev, + usb_sndctrlpipe(dev->udev, 0), + REQ_SET_SLEEP, CMD_VEN_DEV_OUT, + 0, port_id, /* value, index - port */ + NULL, 0, 0); /* data, size, timeout */ + if (retval < 0) { + printk(KERN_ERR "Si700x: failed to turn OFF the sleeping " + "for port %d\n", port_id); + goto error; + } + mutex_unlock(&dev->lock); + return 0; + + } + mutex_unlock(&dev->lock); + return -EINVAL; +error: + mutex_unlock(&dev->lock); + return retval; +} + +static const struct file_operations si700x_fops = { + .open = si700x_open, + .release = si700x_release, + .read = si700x_read, + .write = si700x_write, + .unlocked_ioctl = si700x_ioctl, +}; + +static struct usb_class_driver si700x_class = { + .name = "si700x%d", + .fops = &si700x_fops, + .minor_base = 192, +}; + +static int si700x_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct si700x_dev *dev = NULL; + struct usb_host_interface *iface_desc; + int retval = -ENOMEM; + + pr_debug("Si700x: %s\n", __func__); + + dev = kmalloc(sizeof(struct si700x_dev), GFP_KERNEL); + if (!dev) { + printk(KERN_ERR "Si700x: failed to allocate memory for device\n"); + return -ENOMEM; + } + memset(dev, 0x00, sizeof(dev)); + + mutex_init(&dev->lock); + mutex_lock(&dev->lock); + dev->interface = interface; + dev->udev = interface_to_usbdev(interface); + dev->buffer_size = sizeof(dev->buffer); + + iface_desc = interface->cur_altsetting; + + usb_set_intfdata(interface, dev); + + retval = usb_register_dev(interface, &si700x_class); + if (retval < 0) { + printk(KERN_ERR "Si700x: failed to get minor number\n"); + usb_set_intfdata(interface, NULL); + mutex_unlock(&dev->lock); + kfree(dev); + return retval; + } + mutex_unlock(&dev->lock); + printk(KERN_INFO "Si700x: minor number %d\n", interface->minor); + return 0; +} + +static void si700x_disconnect(struct usb_interface *interface) +{ + struct si700x_dev *dev; + int minor = interface->minor; + + pr_debug("Si700x: %s\n", __func__); + + dev = usb_get_intfdata(interface); + mutex_lock(&dev->lock); + usb_deregister_dev(interface, &si700x_class); + usb_set_intfdata(interface, NULL); + mutex_unlock(&dev->lock); + kfree(dev); + printk(KERN_INFO "Si700x: USB #%d now disconnted\n", minor); +} + +static struct usb_device_id si700x_table[] = { + { USB_DEVICE(0x10c4, 0x8649) }, + {} +}; +MODULE_DEVICE_TABLE(usb, si700x_table); + +static struct usb_driver si700x_driver = { + .name = "si700x", + .probe = si700x_probe, + .disconnect = si700x_disconnect, + .id_table = si700x_table, +}; + +static int __init si700x_init(void) +{ + int retval; + + pr_debug("Si700x: %s\n", __func__); + + retval = usb_register(&si700x_driver); + if (retval) { + printk(KERN_ERR "Si700x: failed to register usb device\n"); + return retval; + } + return 0; +} + +static void __exit si700x_exit(void) +{ + pr_debug("Si700x: %s\n", __func__); + + usb_deregister(&si700x_driver); +} + +module_init(si700x_init); +module_exit(si700x_exit); + +MODULE_AUTHOR("Prashant Shah <pshah.mumbai@xxxxxxxxx>"); +MODULE_DESCRIPTION("Silicon Labs Si700x USB Evaluation Board"); +MODULE_LICENSE("GPL v2"); + diff --git a/si700x.h b/si700x.h new file mode 100644 index 0000000..6778325 --- /dev/null +++ b/si700x.h @@ -0,0 +1,102 @@ +#ifndef _SI700X_H +#define _SI700X_H + +/* IOCTL definitions */ + +#define SI700X_IOC_MAGIC 'k' +#define SI700X_IOC_MAXNR 9 + +#define SI700X_LED_ON _IO(SI700X_IOC_MAGIC, 1) +#define SI700X_LED_OFF _IO(SI700X_IOC_MAGIC, 2) +#define SI700X_VERSION _IOR(SI700X_IOC_MAGIC, 3, int) +#define SI700X_PORT_COUNT _IOR(SI700X_IOC_MAGIC, 4, int) +#define SI700X_BOARDID _IOR(SI700X_IOC_MAGIC, 5, int) +#define SI700X_SETPROG_ON _IO(SI700X_IOC_MAGIC, 6) +#define SI700X_SETPROG_OFF _IO(SI700X_IOC_MAGIC, 7) +#define SI700X_SETSLEEP_ON _IOW(SI700X_IOC_MAGIC, 8, unsigned int) +#define SI700X_SETSLEEP_OFF _IOW(SI700X_IOC_MAGIC, 9, unsigned int) + +#define XFER_TYPE_WRITE 0x10 +#define XFER_TYPE_READ 0x20 +#define XFER_TYPE_WRITE_READ (XFER_TYPE_WRITE|XFER_TYPE_READ) + +/* Slave Address */ +#define SLAVE_NEW 0x40 +#define SLAVE_LEGACY 0X20 + +/* Registers */ +#define REG_STATUS 0x00 +#define REG_DATA 0x01 +#define REG_CFG1 0x03 +#define REG_CFG2 0x04 +#define REG_DEVICE_ID 0x11 +#define REG_RAW_DATA 0x29 + +/* Status Register */ +#define STATUS_NOT_READY 0x01 + +/* Config 1 Register */ +#define CFG1_START_CONV 0x01 +#define CFG1_HUMIDITY 0x04 +#define CFG1_TEMPERATURE 0x10 +#define CFG1_FAST_CONV 0x20 + +/* Config 2 Register */ +#define CFG2_EN_TEST_REG 0x80 + +/* Coefficients */ +#define TEMPERATURE_OFFSET 50 +#define HUMIDITY_OFFSET 16 +#define SLOPE 32 + +/* Return codes */ +#define SUCCESS 0 /* Success */ +#define ERROR_LENGTH_BAD -1 /* DataLength is not valid */ +#define ERROR_INIT_FAIL -2 /* Can not initialize slave */ +#define ERROR_READ_FAIL -3 /* Can not read from slave */ +#define ERROR_WRITE_FAIL -4 /* Can not write to slave */ +#define ERROR_TIME_OUT -5 /* Timed out waiting for ready */ + +/* Transfer status */ +#define XFER_STATUS_NONE 0x00 +#define XFER_STATUS_SUCCESS 0x01 +#define XFER_STATUS_ADDR_NAK 0x02 +#define XFER_STATUS_DATA_NAK 0x03 +#define XFER_STATUS_TIMEOUT 0x04 +#define XFER_STATUS_ARBLOST 0x05 +#define XFER_STATUS_BAD_LENGTH 0x06 +#define XFER_STATUS_BAD_MODE 0x07 +#define XFER_STATUS_BAD_STATE 0x09 + +/* Pipes */ +#define PIPE_DATA_OUT 0x02 +#define PIPE_DATA_IN 0x82 + +/* Request type */ +#define CMD_VEN_DEV_OUT 0x40 +#define CMD_VEN_DEV_IN 0xC0 + +/* Request */ +#define REQ_GET_VERSION 0 +#define REQ_SET_LED 1 +#define REQ_SET_SLEEP 2 +#define REQ_SET_PROG 3 +#define REQ_GET_PORT_COUNT 4 +#define REQ_GET_BOARD_ID 5 + +/* Maximum data bytes that can be transfered with ReadData() and WriteData() */ +#define MAX_DATA_LENGTH 3 + +/* Maximum number of slaves on a Si7001 board */ +#define MAX_SLAVE_COUNT 8 + +/* Maximum number of bytes in a packet */ +#define MAX_PACKET_SIZE 64 + +/* Maximum number of I2C data bytes that can be read or written */ +#define MAX_XFER_LENGTH 4 + +/* Maximum number of transfer requests in a packet */ +#define MAX_XFER_COUNT (MAX_PACKET_SIZE/(4+MAX_XFER_LENGTH)) + +#endif -- 1.7.5.4 _______________________________________________ devel mailing list devel@xxxxxxxxxxxxxxxxxxxxxx http://driverdev.linuxdriverproject.org/mailman/listinfo/devel