blob: f0eb6ae7c918593871ac5ae666d734909832d863 [file] [log] [blame]
/*
* adis16505.c - Driver for the adis16505 IMU used by 971.
*/
#include <linux/cdev.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/kernel.h> /* Needed for pr_info() */
#include <linux/kfifo.h>
#include <linux/module.h> /* Needed by all modules */
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/poll.h>
#include <linux/spi/spi.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("frc971");
MODULE_DESCRIPTION("adis16505 rp2040 driver");
#define MODULE_NAME "adis16505"
//! filter for the device tree class
static struct of_device_id adis16505_match[] = {
{.compatible = "frc971,adis16505"}, {}};
MODULE_DEVICE_TABLE(of, adis16505_match);
#define TRANSFER_SIZE 42
struct imu_sample {
char time[8];
char d[TRANSFER_SIZE];
};
struct adis16505_state {
dev_t character_device;
struct class *device_class;
struct cdev handle_cdev;
struct spi_device *spi;
struct spi_message spi_msg;
struct spi_transfer spi_xfer;
char tx_buff[128];
char rx_buff[128];
atomic_t count;
spinlock_t lock;
wait_queue_head_t wq;
spinlock_t fifo_read_lock;
DECLARE_KFIFO(fifo, struct imu_sample, 32);
};
static int adis16505_dev_open(struct inode *in, struct file *f) {
struct adis16505_state *ts =
container_of(in->i_cdev, struct adis16505_state, handle_cdev);
int count = 0;
f->private_data = ts;
count = atomic_cmpxchg(&ts->count, 0, 1);
if (count > 0) {
return -EBUSY;
}
// Enable the IRQ after we've declared the device open.
enable_irq(ts->spi->irq);
return 0;
}
static int adis16505_dev_release(struct inode *in, struct file *f) {
struct adis16505_state *ts;
ts = container_of(in->i_cdev, struct adis16505_state, handle_cdev);
// Disable before declaring ourselves closed so we don't fire the IRQ when we
// are disabled, or let someone else open it up again before we disable it.
disable_irq(ts->spi->irq);
if (atomic_cmpxchg(&ts->count, 1, 0) != 1) {
BUG_ON(true);
}
return 0;
}
static ssize_t adis16505_dev_read(struct file *f, char *d, size_t s,
loff_t *of) {
struct adis16505_state *ts = f->private_data;
int err;
if (s != sizeof(struct imu_sample)) {
return -EINVAL;
}
while (true) {
struct imu_sample sample;
int elements;
spin_lock(&ts->fifo_read_lock);
elements = kfifo_get(&ts->fifo, &sample);
spin_unlock(&ts->fifo_read_lock);
if (elements == 0) {
bool empty;
if (f->f_flags & O_NONBLOCK) {
return -EAGAIN;
}
err = wait_event_interruptible(
ts->wq,
(spin_lock(&ts->fifo_read_lock), empty = !kfifo_is_empty(&ts->fifo),
spin_unlock(&ts->fifo_read_lock), empty));
if (err != 0) {
return err;
}
continue;
}
memcpy(d, &sample, sizeof(sample));
return sizeof(sample);
}
}
static unsigned int adis16505_dev_poll(struct file *f,
struct poll_table_struct *wait) {
struct adis16505_state *ts = f->private_data;
__poll_t mask = 0;
poll_wait(f, &ts->wq, wait);
spin_lock(&ts->fifo_read_lock);
if (!kfifo_is_empty(&ts->fifo)) {
mask |= (POLLIN | POLLRDNORM);
}
spin_unlock(&ts->fifo_read_lock);
return mask;
}
static const struct file_operations adis16505_cdev_opps = {
.read = adis16505_dev_read,
.open = adis16505_dev_open,
.release = adis16505_dev_release,
.poll = adis16505_dev_poll,
};
static void all_done(void *ts_ptr) {
// struct adis16505_state *ts = ts_ptr;
// printk("All done %x %x\n", ts->rx_buff[0], ts->rx_buff[1]);
}
static irqreturn_t adis16505_irq(int irq, void *handle) {
struct adis16505_state *ts = handle;
struct imu_sample s;
int err;
int i;
u64 time = ktime_get_ns();
memcpy(&s.time, &time, sizeof(time));
spi_message_init(&ts->spi_msg);
for (i = 0; i < TRANSFER_SIZE; ++i) {
ts->tx_buff[i] = i;
}
ts->spi_xfer.tx_buf = ts->tx_buff;
ts->spi_xfer.rx_buf = ts->rx_buff;
ts->spi_xfer.len = TRANSFER_SIZE;
spi_message_add_tail(&ts->spi_xfer, &ts->spi_msg);
ts->spi_msg.complete = all_done;
ts->spi_msg.context = ts;
err = spi_sync(ts->spi, &ts->spi_msg);
// TODO(austin): Timestamp. Also decode the packet for real.
for (i = 0; i < TRANSFER_SIZE; ++i) {
s.d[i] = ts->rx_buff[i];
}
// Attempt to emplace. If it fails, just drop the data.
kfifo_put(&ts->fifo, s);
wake_up_interruptible(&ts->wq);
return IRQ_HANDLED;
}
static int adis16505_probe(struct spi_device *spi) {
int err;
struct adis16505_state *ts;
if (!spi->irq) {
dev_dbg(&spi->dev, "no IRQ?\n");
return -EINVAL;
}
if (spi->max_speed_hz > 10000000) {
dev_err(&spi->dev, "f(sample) %d KHz?\n", spi->max_speed_hz / 1000);
return -EINVAL;
}
spi->bits_per_word = 8;
spi->mode = SPI_MODE_3;
spi->max_speed_hz = 2000000;
err = spi_setup(spi);
if (err < 0) {
return err;
}
ts = kzalloc(sizeof(struct adis16505_state), GFP_KERNEL);
if (!ts) {
err = -ENOMEM;
goto err_free_mem;
}
printk("Ts allocated %p\n", ts);
spin_lock_init(&ts->lock);
spin_lock_init(&ts->fifo_read_lock);
atomic_set(&ts->count, 0);
INIT_KFIFO(ts->fifo);
init_waitqueue_head(&ts->wq);
spi_set_drvdata(spi, ts);
ts->spi = spi;
// Flags are sourced from the device tree.
err = request_threaded_irq(spi->irq, NULL, adis16505_irq, IRQF_ONESHOT,
spi->dev.driver->name, ts);
if (err < 0) {
dev_dbg(&spi->dev, "irq %d busy?\n", spi->irq);
goto err_free_mem;
}
// Immediately disable the IRQ. Opening the device will enable it, so this
// lets us leave the driver probed all the time and only use it when userspace
// asks.
//
// Note: the IRQ will fire probably once before we get it disabled... It
// might initiate a transfer from a device which isn't connected, which should
// just return 0 for everything. This isn't actually a huge concern.
disable_irq(spi->irq);
err = alloc_chrdev_region(&ts->character_device, 0, 1, "adis16505");
if (err < 0) {
dev_dbg(&spi->dev, "alloc_chrdev_region error %i", err);
goto err_free_irq;
}
// create device class
if ((ts->device_class = class_create(THIS_MODULE, "adis16505_class")) ==
NULL) {
dev_dbg(&spi->dev, "class_create error");
goto error_classCreate;
}
if (NULL == device_create(ts->device_class, NULL, ts->character_device, NULL,
"adis16505")) {
dev_dbg(&spi->dev, "device_create error");
goto error_deviceCreate;
}
cdev_init(&ts->handle_cdev, &adis16505_cdev_opps);
err = cdev_add(&ts->handle_cdev, ts->character_device, 1);
if (-1 == err) {
dev_dbg(&spi->dev, "cdev_add error %i", err);
goto error_device_add;
return -1;
}
dev_dbg(&spi->dev, "Probed adis16505\n");
if (err < 0) {
goto err_free_mem;
}
return 0;
error_device_add:
device_destroy(ts->device_class, ts->character_device);
error_deviceCreate:
class_destroy(ts->device_class);
error_classCreate:
unregister_chrdev_region(ts->character_device, 1);
err_free_irq:
free_irq(spi->irq, ts);
err_free_mem:
kfree(ts);
return err;
}
static void adis16505_remove(struct spi_device *spi) {
struct adis16505_state *ts = spi_get_drvdata(spi);
device_destroy(ts->device_class, ts->character_device);
class_destroy(ts->device_class);
unregister_chrdev_region(ts->character_device, 1);
free_irq(spi->irq, ts);
kfree(ts);
dev_dbg(&spi->dev, "unregistered adis16505\n");
}
static struct spi_driver adis16505_driver = {
.driver =
{
.name = "adis16505",
.of_match_table = of_match_ptr(adis16505_match),
},
.probe = adis16505_probe,
.remove = adis16505_remove,
};
module_spi_driver(adis16505_driver);