Pni11096 spi.c
From GumstixDocsWiki
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);

