Pni11096 spi.c

From GumstixDocsWiki

Jump to: navigation, search

This is drivers/spi/pni11096_spi.c

/*
 * Copyright (C) 2006 Andrei Rylin
 *
 * 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.
 */

#include <linux/init.h>
#include <linux/poll.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/dma-mapping.h>
#include <linux/spi/spi.h>
#include <asm/uaccess.h>

#define DEV_NAME "pni11096-spi"
#define DEV_NAME_LONG "PNI-11096 3-axis magneto-inductive sensor"
#define DRV_VERSION_STR "version 01.00"

MODULE_AUTHOR("Andrei Rylin <port777@gmail.com>");
MODULE_DESCRIPTION(DEV_NAME_LONG);
MODULE_LICENSE("GPL");

#define CMD_DHST 0x80 /* high speed oscillator test */
#define CMD_PS2  0x40 /* period select */
#define CMD_PS1  0x20
#define CMD_PS0  0x10
#define CMD_ODIR 0x08 /* oscillator direction */
#define CMD_MOT  0x04 /* magnetic oscillator test */
#define CMD_AS1  0x02 /* axis select */
#define CMD_AS0  0x01

#define CMD_PS_MASK 0x70
#define CMD_AS_MASK 0x03
#define CMD_AS_2MHZ 0
#define CMD_AS_X    1
#define CMD_AS_Y    2
#define CMD_AS_Z    3

#define ALIGNED(x)  __attribute__((aligned(x)))
#define IS_DMA_ALIGNED(x) (((u32)(x)&7)==0)
#define N_BUFS 2

#define PROC_MODE    0x00
#define PROC_STAT    0x01
#define PROC_PERF    0x02
#define PROC_PERF1   0x03
#define PROC_PERIOD  0x04
#define PROC_WHOAMI  0x05

#define FLAGS_MODE_HEX  0x01
#define FLAGS_MODE_BIN  0x02

struct this_proc_entry {
    char id;
    const char* name;
};

struct this_data {
    struct this_dev* dev;
    struct list_head link;
    u16 buf[3];
};

struct cmd_msg {
    struct this_dev* dev;
    struct spi_message msg;
    struct spi_transfer xfer;
    int axis;
    ALIGNED(8) u8 command;
};

struct dat_msg {
    struct this_dev* dev;
    struct spi_message msg;
    struct spi_transfer xfer;
    int axis;
    ALIGNED(8) u16 data;
};

static const struct this_proc_entry this_proc_entries[] = {
    {PROC_MODE,    "mode"},
    {PROC_STAT,    "stat"},
    {PROC_PERF,    "perf"},
    {PROC_PERF1,   "perf1"},
    {PROC_PERIOD,  "period"},
    {PROC_WHOAMI,  "whoami"},
};

struct this_dev {
    struct list_head link;
    int minor;
    int flags;
    int shift; /* pre-calculated period scaling */
    struct class_device *class_dev;
    struct spi_device* spi;

    spinlock_t lock;
    struct this_data data_buf[N_BUFS];
    struct list_head free_data;
    struct list_head pending_data;

    struct list_head clients;
    wait_queue_head_t waitq;

    struct cmd_msg cmd[3]; 
    struct list_head free_cmd;

    struct dat_msg dat[3]; 
    struct list_head free_dat;

    unsigned long samples;
    unsigned long overruns;

    struct proc_dir_entry *proc_parent;
    struct proc_dir_entry *proc_entry[ARRAY_SIZE(this_proc_entries)];
};

struct this_proc_data {
    const struct this_proc_entry* entry;
    struct this_dev* dev;
};

struct this_client {
    struct list_head link;
    struct this_dev* dev;
};

static const char* dev_mode[] = { "dec", "hex", "bin", "bin" };
static const char* dev_mode_fmt[] = {
    "%hd %hd %hd\n", "%#.4hx %#.4hx %#.4hx\n",
};

static struct class* this_dev_class;
static int this_major;
static LIST_HEAD(this_dev_list);
static DEFINE_SPINLOCK(this_dev_list_lock);

static int this_read_proc(char *p, char **start, off_t off,
    int count, int *eof, void *_data)
{
    struct this_proc_data* data = _data;
    struct this_dev* dev = data->dev;
    unsigned long flags, samples, overruns;
    int i;

    *eof = 1;
    *start = p;

    if (off && data->entry->id != PROC_PERF)
        return 0;

    switch (data->entry->id) {
    case PROC_WHOAMI:
        p += sprintf(p, "device: " DEV_NAME_LONG "\n");
        p += sprintf(p, "driver: " DRV_VERSION_STR "\n");
        break;
    case PROC_MODE:
        spin_lock_irqsave(&dev->lock, flags);
        i = dev->flags & 3;
        spin_unlock_irqrestore(&dev->lock, flags);
        p += sprintf(p, "%s\n", dev_mode[i]);
        break;
    case PROC_STAT:
        spin_lock_irqsave(&dev->lock, flags);
        samples = dev->samples;
        overruns = dev->overruns;
        spin_unlock_irqrestore(&dev->lock, flags);
        p += sprintf(p, "%ld samples %ld overruns\n",
            samples, overruns);
        break;
    case PROC_PERIOD:
        spin_lock_irqsave(&dev->lock, flags);
        i = 7 - dev->shift;
        spin_unlock_irqrestore(&dev->lock, flags);
        p += sprintf(p, "%d\n", i);
        break;
    case PROC_PERF:
    case PROC_PERF1:
        spin_lock_irqsave(&dev->lock, flags);
        samples = dev->samples;
        overruns = dev->overruns;
        spin_unlock_irqrestore(&dev->lock, flags);
        flags = schedule_timeout_interruptible(msecs_to_jiffies(1000));
        if (flags)
            return flags;
        spin_lock_irqsave(&dev->lock, flags);
        samples = dev->samples - samples;
        overruns = dev->overruns - overruns;
        spin_unlock_irqrestore(&dev->lock, flags);
        p += sprintf(p, "%ld samples %ld overruns per second\n",
            samples, overruns);
        break;
    default:
        return -EINVAL;
    }
    return p-*start;
}

static int this_write_proc(struct file *file, const char __user *buf,
    unsigned long count, void *_data)
{
    struct this_proc_data* data = _data;
    struct this_dev* dev = data->dev;
    char c, lbuf[16], *p = lbuf;
    unsigned long flags;

    if (!capable(CAP_SYS_ADMIN))
        return -EACCES;

    if (count>15)
        return -EINVAL;

    if (copy_from_user(lbuf, buf, count))
        return -EFAULT;
    lbuf[count] = 0;

    while(*p && (isspace(*p) || ispunct(*p)))
        p++;

    switch (data->entry->id) {
    case PROC_MODE:
        spin_lock_irqsave(&dev->lock, flags);
        switch (tolower(*p)) {
        case 'b':
            dev->flags |= FLAGS_MODE_BIN;
            break;
        case 'h':
            dev->flags &= ~FLAGS_MODE_BIN;
            dev->flags |= FLAGS_MODE_HEX;
            break;
        case 'd':
            dev->flags &= ~(FLAGS_MODE_BIN|FLAGS_MODE_HEX);
            break;
        default:
            spin_unlock_irqrestore(&dev->lock, flags);
            dev_info(&dev->spi->dev, "expect [dec|hex|bin]\n");
            return -EINVAL;
        }
        spin_unlock_irqrestore(&dev->lock, flags);
        break;
    case PROC_PERIOD:
        c = simple_strtoul(p, NULL, 0) & 7;
        spin_lock_irqsave(&dev->lock, flags);
        dev->shift = 7 - c;
        c <<= 4;
        {
            int i;
            for(i=0;i<3;i++) {
                dev->cmd[i].command &= ~CMD_PS_MASK;
                dev->cmd[i].command |= c;
            }
        }
        spin_unlock_irqrestore(&dev->lock, flags);
        break;
    case PROC_STAT:
        spin_lock_irqsave(&dev->lock, flags);
        dev->samples = 0;
        dev->overruns = 0;
        spin_unlock_irqrestore(&dev->lock, flags);
        break;
    default:
        return -EINVAL;
    }
    return count;
}

static inline void __this_start(struct this_dev *dev)
{
    struct cmd_msg* cmd;
    BUG_ON(list_empty(&dev->free_cmd));
    cmd = list_entry(dev->free_cmd.next, struct cmd_msg, msg.queue);
    list_del_init(&cmd->msg.queue);
    spi_async(dev->spi, &cmd->msg);
}

static inline void this_start(struct this_dev *dev)
{
    unsigned long flags;
    spin_lock_irqsave(&dev->lock, flags);
    __this_start(dev);
    spin_unlock_irqrestore(&dev->lock, flags);
}

void this_cmd_complete(void *context)
{
    unsigned long flags;
    struct cmd_msg* cmd = context;
    struct this_dev* dev = cmd->dev;

    spin_lock_irqsave(&dev->lock, flags);
    list_add_tail(&cmd->msg.queue, &dev->free_cmd);
    spin_unlock_irqrestore(&dev->lock, flags);
}

void this_data_complete(void *context)
{
    unsigned long flags;
    struct dat_msg* dat = context;
    struct this_dev* dev = dat->dev;
    struct this_data* data;
    struct cmd_msg* cmd;

    spin_lock_irqsave(&dev->lock, flags);
    if (list_empty(&dev->free_data)) {
        BUG_ON(list_empty(&dev->pending_data));
        dev->overruns++;
        list_move_tail(dev->pending_data.next, &dev->free_data);
    }
    data = list_entry(dev->free_data.next, struct this_data, link);
    data->buf[dat->axis] = ntohs(dat->data) << dev->shift;

    list_add_tail(&dat->msg.queue, &dev->free_dat);
    if (dat->axis == 2) {
        if (list_empty(&dev->clients)) {
            spin_unlock_irqrestore(&dev->lock, flags);
            return;
        }
        list_move_tail(dev->free_data.next, &dev->pending_data);
        wake_up_interruptible(&dev->waitq);
    }

    BUG_ON(list_empty(&dev->free_cmd));
    cmd = list_entry(dev->free_cmd.next, struct cmd_msg, msg.queue);
    list_del_init(&cmd->msg.queue);
    spin_unlock_irqrestore(&dev->lock, flags);
    spi_async(dev->spi, &cmd->msg);
}

static inline
struct this_dev* __get_this_dev_by_minor(int minor)
{
    struct this_dev* dev;
    list_for_each_entry(dev, &this_dev_list, link)
        if (dev->minor == minor)
            return dev;
    return NULL;
}

static inline
struct this_dev* get_this_dev_by_minor(int minor)
{
    struct this_dev* dev;
    spin_lock(&this_dev_list_lock);
    dev = __get_this_dev_by_minor(minor);
    spin_unlock(&this_dev_list_lock);
    return dev;
}

#define DELAY_uSECS 0

static
void this_data_init(struct this_data* data, struct this_dev* dev)
{
    data->dev = dev;
    list_add_tail(&data->link, &dev->free_data);
}

static void cmd_init(struct cmd_msg* cmd, struct this_dev* dev, int axis)
{
    cmd->dev = dev;
    cmd->axis = axis;
    cmd->command = axis + 1;

    spi_message_init(&cmd->msg);

    cmd->msg.complete = this_cmd_complete;
    cmd->msg.context = cmd;
    cmd->msg.is_dma_mapped = 1;

    cmd->xfer.len = 1;
    cmd->xfer.tx_buf = &cmd->command;
    BUG_ON(!IS_DMA_ALIGNED(&cmd->command));
    cmd->xfer.cs_change = 1;
    cmd->xfer.cs_reset = 1;
    cmd->xfer.delay_usecs = DELAY_uSECS;
    cmd->xfer.tx_dma = dma_map_single(&dev->spi->dev,
        (void*)cmd->xfer.tx_buf, 1, DMA_FROM_DEVICE);

    spi_message_add_tail(&cmd->xfer, &cmd->msg);
    list_add_tail(&cmd->msg.queue, &dev->free_cmd);
}

static void dat_init(struct dat_msg* dat, struct this_dev* dev, int axis)
{
    dat->dev = dev;
    dat->axis = axis;

    spi_message_init(&dat->msg);

    dat->msg.complete = this_data_complete;
    dat->msg.context = dat;
    dat->msg.is_dma_mapped = 1;

    dat->xfer.len = 2;
    dat->xfer.rx_buf = &dat->data;
    BUG_ON(!IS_DMA_ALIGNED(&dat->data));
    dat->xfer.cs_change = 1;
    dat->xfer.delay_usecs = DELAY_uSECS;
    dat->xfer.rx_dma = dma_map_single(&dev->spi->dev,
        (void*)dat->xfer.rx_buf, 2, DMA_TO_DEVICE);

    spi_message_add_tail(&dat->xfer, &dat->msg);
    list_add_tail(&dat->msg.queue, &dev->free_dat);
}

static inline void cmd_deinit(struct cmd_msg* cmd)
{
    dma_unmap_single(&cmd->dev->spi->dev, cmd->xfer.tx_dma,
        1, DMA_FROM_DEVICE);
}

static inline void dat_deinit(struct dat_msg* dat)
{
    dma_unmap_single(&dat->dev->spi->dev, dat->xfer.rx_dma,
        2, DMA_TO_DEVICE);
}

static
struct this_dev* new_this_dev(struct spi_device* spi)
{
    int i;
    struct this_dev* dev;
    dev = kzalloc(sizeof *dev, GFP_KERNEL);
    if (!dev)
        return ERR_PTR(-ENOMEM);

    dev->spi = spi;

    spin_lock_init(&dev->lock);
    INIT_LIST_HEAD(&dev->free_data);
    INIT_LIST_HEAD(&dev->pending_data);

    INIT_LIST_HEAD(&dev->free_cmd);
    INIT_LIST_HEAD(&dev->free_dat);

    for (i=0; i<3; i++) {
        cmd_init(&dev->cmd[i], dev, i);
        dat_init(&dev->dat[i], dev, i);
    }

    for (i=0; i<N_BUFS; i++)
        this_data_init(&dev->data_buf[i], dev);

    /* dev->flags = 0: default mode dec, period 0 */
    dev->shift = 7;

    INIT_LIST_HEAD(&dev->clients);
    init_waitqueue_head(&dev->waitq);

    spin_lock(&this_dev_list_lock);
    for(dev->minor = 0; dev->minor < 256; dev->minor++)
        if(!__get_this_dev_by_minor(dev->minor))
            break;
    if (dev->minor < 256)
        list_add_tail(&dev->link, &this_dev_list);
    else {
        for (i=0; i<3; i++) {
            cmd_deinit(&dev->cmd[i]);
            dat_deinit(&dev->dat[i]);
        }
        kfree(dev);
        dev = NULL;
    }
    spin_unlock(&this_dev_list_lock);
    return dev;
}

static inline
void return_this_dev(struct this_dev* dev)
{
    int i;
    spin_lock(&this_dev_list_lock); 
    list_del(&dev->link);
    spin_unlock(&this_dev_list_lock);
    for (i=0; i<3; i++) {
        cmd_deinit(&dev->cmd[i]);
        dat_deinit(&dev->dat[i]);
    }
}

static inline
struct this_dev* get_this_dev_by_spi(
    struct spi_device *spi)
{
    struct this_dev* dev;
    spin_lock(&this_dev_list_lock);
    list_for_each_entry(dev, &this_dev_list, link) {
        if (dev->spi == spi) {
            spin_unlock(&this_dev_list_lock);
            return dev;
        }
    }
    spin_unlock(&this_dev_list_lock);
    return NULL;
}

static ssize_t show_dev_name(struct class_device *class_dev, char *buf)
{
    struct this_dev* dev = get_this_dev_by_minor(MINOR(class_dev->devt));
    if (!dev)
        return -ENODEV;
    return sprintf(buf, DEV_NAME "%d.%d\n",
        dev->spi->master->bus_num, dev->spi->chip_select);
}   
static CLASS_DEVICE_ATTR(name, S_IRUGO, show_dev_name, NULL);

static ssize_t this_read(struct file *file, char __user *buf,
     size_t count, loff_t *offset)
{
    int ret;
    unsigned long flags;
    struct this_client *client = file->private_data;
    struct this_dev *dev = client->dev;
    struct this_data* data;
    u16 d[3];

again:
    if (list_empty(&dev->pending_data) && file->f_flags & O_NONBLOCK)
        return -EAGAIN;

    ret = wait_event_interruptible(dev->waitq,
                                    !list_empty(&dev->pending_data));
    if (ret)
        return ret;

    spin_lock_irqsave(&dev->lock, flags);
    if (list_empty(&dev->pending_data)) {
        spin_unlock_irqrestore(&dev->lock, flags);
        goto again;
    }
    data = list_entry(dev->pending_data.next, struct this_data, link);
    memcpy(d, data->buf, 6);
    list_move_tail(dev->pending_data.next, &dev->free_data);
    dev->samples++;
    spin_unlock_irqrestore(&dev->lock, flags);
 
    if (dev->flags & FLAGS_MODE_BIN) {
        copy_to_user(buf, d, 6);
        ret = 6;
    } else {
        ret = sprintf(buf, dev_mode_fmt[dev->flags & FLAGS_MODE_HEX],
            d[0], d[1], d[2]);
    }
    *offset = 0;
    BUG_ON(ret > count);
    return ret;
}

static unsigned int this_poll(struct file *file, poll_table *wait)
{
    struct this_client *client = file->private_data;
    struct this_dev *dev = client->dev;

    poll_wait(file, &dev->waitq, wait);

    if (!list_empty(&dev->pending_data))
        return POLLIN | POLLRDNORM;

    return 0;
}

static int this_open(struct inode *inode, struct file *file)
{
    struct this_dev* dev;
    struct this_client* client;
    int first = 0;

    dev = get_this_dev_by_minor(iminor(inode));
    if (!dev)
        return -ENODEV;
    client = kzalloc(sizeof(*client), GFP_KERNEL);
    if (!client)
        return -ENOMEM;
    client->dev = dev;
    file->private_data = client;
    INIT_LIST_HEAD(&client->link);

    spin_lock(&dev->lock);
    if (list_empty(&dev->clients))
        first = 1;
    list_add_tail(&client->link, &dev->clients);
    if (first)
        this_start(dev);
    spin_unlock(&dev->lock);

    return 0;
}

static int this_release(struct inode *inode, struct file *file)
{
    struct this_client *client = file->private_data;
    struct this_dev *dev = client->dev;

    spin_lock(&dev->lock);
    list_del(&client->link);
    spin_unlock(&dev->lock);

    kfree(client);
    file->private_data = NULL;
    return 0;
}

static irqreturn_t this_interrupt(int irq, void* context, struct pt_regs* regs)
{
    struct this_dev* dev = context;
    struct dat_msg* dat;

    spin_lock(&dev->lock);
    BUG_ON(list_empty(&dev->free_dat));
    dat = list_entry(dev->free_dat.next, struct dat_msg, msg.queue);
    list_del_init(&dat->msg.queue);
    BUG_ON(dat->msg.status);
    spin_unlock(&dev->lock);

    spi_async(dev->spi, &dat->msg);
    return IRQ_HANDLED;
}

static int this_probe(struct spi_device *spi)
{
    struct this_dev* dev;
    int i;

    i = spi_setup(spi);
    if (i)
        return i;

    dev = new_this_dev(spi);
    if (IS_ERR(dev))
        return PTR_ERR(dev);

    dev->class_dev = class_device_create(this_dev_class, NULL,
        MKDEV(this_major, dev->minor), &spi->dev,
        DEV_NAME "%d.%d", spi->master->bus_num, spi->chip_select);
    if (!dev->class_dev)
        goto error;

    class_device_create_file(dev->class_dev, &class_device_attr_name);

    i = request_irq(spi->irq, this_interrupt, IRQF_TRIGGER_RISING,
                    dev->class_dev->class_id, dev);
    if (i < 0)
        goto error_class_destroy;

    dev->proc_parent = create_proc_entry(dev->class_dev->class_id,
        S_IFDIR | S_IRUGO | S_IXUGO, NULL);
    if (!dev->proc_parent) {
        dev_err(&spi->dev, "failed to create /proc/%s\n",
            dev->class_dev->class_id);
        goto error_free_irq;
    }
    for(i=0; i<ARRAY_SIZE(this_proc_entries); i++) {
        dev->proc_entry[i] = create_proc_entry( 
            this_proc_entries[i].name, 0644, dev->proc_parent);
        if (dev->proc_entry[i]) {
            struct this_proc_data* data =
                kmalloc(sizeof *data, GFP_KERNEL);
            if (data) {
                data->entry = &this_proc_entries[i];
                data->dev = dev;
            }
            dev->proc_entry[i]->data = data;
            dev->proc_entry[i]->read_proc = this_read_proc;
            dev->proc_entry[i]->write_proc = this_write_proc;
        } else {
            dev_err(&spi->dev, "failed to create /proc/%s/%s\n",
                dev->class_dev->class_id, this_proc_entries[i].name);
        }
    }

    return 0;

error_free_irq:
    free_irq(spi->irq, dev);
error_class_destroy:
    class_device_destroy(this_dev_class, MKDEV(this_major, dev->minor));
error:
    return_this_dev(dev);
    kfree(dev);
    return -ENODEV;
}

static
void this_shutdown(struct spi_device* spi)
{
    dev_dbg(&spi->dev, "shutdown\n");
}

#ifdef CONFIG_PM
#warning TODO all power management
static
int this_suspend(struct spi_device* spi, pm_message_t state)
{
    struct this_dev* dev = get_this_dev_by_spi(spi);
    switch (state.event) {
        case PM_EVENT_ON:
            dev_dbg(&spi->dev, "power on\n");
            break;
        case PM_EVENT_FREEZE:
            dev_dbg(&spi->dev, "power freeze\n");
            break;
        case PM_EVENT_SUSPEND:
            dev_dbg(&spi->dev, "power suspend\n");
            break;
        default:
            dev_dbg(&spi->dev, "power %d\n", state.event);
            BUG_ON(1);
    }
    return 0;
}

static
int this_resume(struct spi_device *spi)
{
    dev_dbg(&spi->dev, "power resume\n");
    return this_suspend(spi, PMSG_ON);
}

#else
#define this_suspend NULL
#define this_resume NULL
#endif

int this_remove(struct spi_device* spi)
{
    int i;
    struct this_dev* dev = get_this_dev_by_spi(spi);
    if (!dev)
        return -ENODEV;
    free_irq(spi->irq, dev);
    spin_lock(&dev->lock);
    if (!list_empty(&dev->clients)) {
        spin_unlock(&dev->lock);
        return -EBUSY;
    }
    return_this_dev(dev);
    spin_unlock(&dev->lock);

    BUG_ON(!dev->proc_parent);
    for(i=0; i < ARRAY_SIZE(this_proc_entries); i++) {
        if (dev->proc_entry[i]) {
            kfree(dev->proc_entry[i]->data);
            remove_proc_entry(this_proc_entries[i].name, dev->proc_parent);
        }
    }
    remove_proc_entry(dev->class_dev->class_id, NULL);

    class_device_destroy(this_dev_class, MKDEV(this_major, dev->minor));
    kfree(dev);
    return 0;
}

static struct file_operations this_fops = {
    .owner      = THIS_MODULE,
    .llseek     = no_llseek,
    .read       = this_read,
    .open       = this_open,
    .release    = this_release,
    .poll       = this_poll,
};

static struct spi_driver this_driver = {
    .driver = {
        .name = DEV_NAME,
        .bus = &spi_bus_type,
        .owner = THIS_MODULE,
    }, 
    .probe = this_probe,
    .remove = __devexit_p(this_remove),
    .shutdown = this_shutdown,
    .suspend = this_suspend,
    .resume = this_resume,
};

static int __init this_init(void)
{
    int res;
    res = register_chrdev(0, DEV_NAME, &this_fops);
    if (IS_ERR_VALUE(res))
        goto out;
    this_major = res;
    this_dev_class = class_create(THIS_MODULE, DEV_NAME "-dev");
    if (IS_ERR(this_dev_class))
        goto out_unreg_chrdev;
    res = spi_register_driver(&this_driver);
    if (res)
        goto out_unreg_class;
    return 0;

out_unreg_class:
    class_destroy(this_dev_class);
out_unreg_chrdev:
    unregister_chrdev(this_major, DEV_NAME);
out:
    printk(KERN_ERR DEV_NAME ": driver init failed\n");
    return res;
}
module_init(this_init);

static void __exit this_exit(void)
{
    spi_unregister_driver(&this_driver);
    class_destroy(this_dev_class);
    unregister_chrdev(this_major, DEV_NAME);
}
module_exit(this_exit);
Personal tools