diff options
Diffstat (limited to 'yocto/esp32mod')
| -rwxr-xr-x | yocto/esp32mod/Makefile | 15 | ||||
| -rwxr-xr-x | yocto/esp32mod/esp32.c | 535 | ||||
| -rwxr-xr-x | yocto/esp32mod/esp32.h | 16 | ||||
| -rwxr-xr-x | yocto/esp32mod/msgq.c | 50 | ||||
| -rwxr-xr-x | yocto/esp32mod/msgq.h | 15 |
5 files changed, 631 insertions, 0 deletions
diff --git a/yocto/esp32mod/Makefile b/yocto/esp32mod/Makefile new file mode 100755 index 0000000..31b58fb --- /dev/null +++ b/yocto/esp32mod/Makefile @@ -0,0 +1,15 @@ +obj-m := esp32mod.o +esp32mod-y := esp32.o msgq.o + +SRC := $(shell pwd) + +all: + $(MAKE) -C $(KERNEL_SRC) M=$(SRC) + +modules_install: + $(MAKE) -C $(KERNEL_SRC) M=$(SRC) modules_install + +clean: + rm -f *.o *~ core .depend .*.cmd *.ko *.mod.c *.mod + rm -f Module.markers Module.symvers modules.order + rm -rf .tmp_versions Modules.symvers diff --git a/yocto/esp32mod/esp32.c b/yocto/esp32mod/esp32.c new file mode 100755 index 0000000..8c7b5e4 --- /dev/null +++ b/yocto/esp32mod/esp32.c @@ -0,0 +1,535 @@ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/jiffies.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/completion.h> +#include <linux/kthread.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/cdev.h> +#include <linux/spi/spi.h> +#include <linux/gpio.h> + +#include <linux/of_device.h> +#include <linux/uaccess.h> + +#include "msgq.h" +#include "esp32.h" + +#define wait_event_interruptible_lock(wq_head, condition, lock) \ +({ \ + int __ret = 0; \ + if (!(condition)) \ + __ret = ___wait_event(wq_head, condition, TASK_INTERRUPTIBLE, 0, 0, \ + spin_unlock(&lock); \ + schedule(); \ + spin_lock(&lock)); \ + __ret; \ +}) + +/* has to be power of 2 */ +#define MSGQ_SIZE_SND 128 +#define MSGQ_SIZE_RCV 128 + +#define ESP32_SIZE_HDR 3 +#define ESP32_SIZE_BUF (ESP32_MTU + 4) + +static int esp32_open(struct inode *inode, struct file *file); +static int esp32_release(struct inode *inode, struct file *file); +static ssize_t esp32_read(struct file *file, char __user *buf, size_t len, loff_t *off); +static ssize_t esp32_write(struct file *file, const char __user *buf, size_t len, loff_t *off); + +static int esp32_probe(struct spi_device *spi); +static void esp32_remove(struct spi_device *spi); + +static DECLARE_WAIT_QUEUE_HEAD(xchg_wq); +static DECLARE_WAIT_QUEUE_HEAD(cts_wq); + +static MSGQueue snd_msgq; +static MSGQueue rcv_msgq[ESP32_MAX_MTYPE]; + +static struct spi_device *esp32_spi_dev; +static struct gpio_desc *gpio_rts, *gpio_cts; +static int irq_rts, irq_cts; +static int cts_val; +static struct task_struct *kthd; + +static dev_t dev; +static struct class *dev_class; +static struct cdev dev_cdev; +static struct file_operations dev_fops = { + .owner = THIS_MODULE, + .open = esp32_open, + .release = esp32_release, + .read = esp32_read, + .write = esp32_write, +}; + +static const struct of_device_id esp32_of_match[] = { + { + .compatible = "mikrophone,esp32", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, esp32_of_match); + +static const struct spi_device_id esp32_spi_ids[] = { + { "esp32", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(spi, esp32_spi_ids); + +static struct spi_driver esp32_spi_driver = { + .probe = esp32_probe, + .remove = esp32_remove, + .driver = { + .name = "esp32", + .of_match_table = of_match_ptr(esp32_of_match), + }, + .id_table = esp32_spi_ids, +}; + +static irqreturn_t rts_irq_handler(int irq, void *dev_id) { + wake_up(&xchg_wq); + + return IRQ_HANDLED; +} + +static irqreturn_t cts_irq_handler(int irq, void *dev_id) { + cts_val = 0; + wake_up(&cts_wq); + + return IRQ_HANDLED; +} + +/* esp32 dma workaraund */ +static inline unsigned int esp32_fix_len(unsigned int len) { + if (len < 8) { + return 8; + } else if (len % 4 != 0) { + return (len / 4 + 1) * 4; + } + return len; +} + +static inline int wait4cts(void) { + return wait_event_interruptible(cts_wq, !cts_val); +} + +static inline int spi_xchg(struct spi_transfer *spi_tr) { + cts_val = 1; + return spi_sync_transfer(esp32_spi_dev, spi_tr, 1); +} + +static int xchg_thd(void *arg) { + unsigned char *buffer, *_buffer, *buf_rx; + unsigned char mtype, mtype_flags; + int rv, wake_sndq, wake_rcvq, bfree; + struct spi_transfer spi_tr; + uint16_t len_tx, len_rx; + + _buffer = NULL; + memset(&spi_tr, 0, sizeof(spi_tr)); + + buf_rx = esp32_alloc(); + if (buf_rx == NULL) return -ENOMEM; + + while (!kthread_should_stop()) { + /* preallocate buffer outside of spin lock: GFP_KERNEL allocator can sleep */ + if (_buffer == NULL) { + _buffer = esp32_alloc(); + if (_buffer == NULL) return -ENOMEM; + _buffer[0] = 0; + _buffer[1] = 0; + _buffer[2] = 0; + } + + rv = 0; + wake_sndq = 0; + wake_rcvq = 0; + bfree = 1; + + spin_lock(&snd_msgq.lock); + do { + buffer = msgq_pop(&snd_msgq); + if (buffer && (msgq_len(&snd_msgq) == (msgq_size(&snd_msgq) - 1))) { + wake_sndq = 1; + } + if ((buffer == NULL) && (gpiod_get_value(gpio_rts) == 0)) { + buffer = _buffer; + _buffer = NULL; + } + if (buffer == NULL) { + rv = wait_event_interruptible_lock(xchg_wq, (msgq_len(&snd_msgq) != 0) || (gpiod_get_value(gpio_rts) == 0), snd_msgq.lock); + if (rv) break; + } + } while (buffer == NULL); + spin_unlock(&snd_msgq.lock); + + /* kthread_should_stop() might be true */ + if (rv) continue; + + if (wake_sndq) wake_up(&snd_msgq.wq); + + len_tx = (uint16_t)buffer[1] << 8; + len_tx |= (uint16_t)buffer[2] & 0xFF; + if (len_tx > ESP32_MTU) goto xchg_thd_fin; + + spi_tr.tx_buf = buffer; + spi_tr.rx_buf = buf_rx; + spi_tr.len = len_tx + ESP32_SIZE_HDR; + spi_tr.len = esp32_fix_len(spi_tr.len); + + /* wait for cts */ + rv = wait4cts(); + if (rv) goto xchg_thd_fin; + + /* nothing to send/receive */ + if ((buffer[0] == 0) && gpiod_get_value(gpio_rts)) goto xchg_thd_fin; + + rv = spi_xchg(&spi_tr); + if (rv) goto xchg_thd_fin; + + mtype = buf_rx[0] & ESP32_MTYPE_MASK; + mtype_flags = buf_rx[0] & ~ESP32_MTYPE_MASK; + if (mtype >= ESP32_MAX_MTYPE) goto xchg_thd_fin; + + if (mtype) { + memcpy(buffer, spi_tr.rx_buf, spi_tr.len); + + len_rx = (uint16_t)buffer[1] << 8; + len_rx |= (uint16_t)buffer[2] & 0xFF; + if (len_rx > ESP32_MTU) goto xchg_thd_fin; + + if (len_rx + ESP32_SIZE_HDR > spi_tr.len) { + spi_tr.tx_buf = NULL; + spi_tr.rx_buf = buffer + spi_tr.len; + spi_tr.len = len_rx + ESP32_SIZE_HDR - spi_tr.len; + spi_tr.len = esp32_fix_len(spi_tr.len); + + /* wait for cts, again */ + rv = wait4cts(); + if (rv) goto xchg_thd_fin; + + rv = spi_xchg(&spi_tr); + if (rv) goto xchg_thd_fin; + } + + spin_lock(&rcv_msgq[mtype].lock); + rv = msgq_push(&rcv_msgq[mtype], buffer); + if (!rv && (msgq_len(&rcv_msgq[mtype]) == 1)) { + wake_rcvq = 1; + } + spin_unlock(&rcv_msgq[mtype].lock); + + if (!rv) bfree = 0; + if (wake_rcvq) wake_up(&rcv_msgq[mtype].wq); + } + +xchg_thd_fin: + if (bfree) { + if (_buffer == NULL) { + _buffer = buffer; + _buffer[0] = 0; + _buffer[1] = 0; + _buffer[2] = 0; + } else { + esp32_free(buffer); + } + } + } + + esp32_free(_buffer); + esp32_free(buf_rx); + return 0; +} + +static int esp32_open(struct inode *inode, struct file *file) { + unsigned int major, minor; + + major = MAJOR(dev); + minor = MINOR(dev); + if (imajor(inode) != major) return -EINVAL; + if ((iminor(inode) < minor) || (iminor(inode) >= minor + ESP32_MAX_MTYPE)) return -EINVAL; + file->private_data = (void *)(iminor(inode) - minor); + + return 0; +} + +static int esp32_release(struct inode *inode, struct file *file) { + return 0; +} + +static ssize_t esp32_read(struct file *file, char __user *buf, size_t len, loff_t *off) { + unsigned char mtype; + unsigned char *buffer; + uint16_t msg_len; + unsigned long _rv; + int block, rv = 0; + + block = !(file->f_flags & O_NONBLOCK); + mtype = (unsigned char)(file->private_data); + rv = esp32_receive(mtype, block, &buffer); + if (rv) goto esp32_read_fin; + + msg_len = (uint16_t)buffer[1] << 8; + msg_len |= (uint16_t)buffer[2] & 0xFF; + if (len < msg_len) rv = -EINVAL; + if (rv) goto esp32_read_fin; + + _rv = copy_to_user(buf, buffer + ESP32_SIZE_HDR, msg_len); + if (_rv) rv = -EFAULT; + +esp32_read_fin: + if (buffer) esp32_free(buffer); + if (rv) return rv; + return msg_len; +} + +static ssize_t esp32_write(struct file *file, const char __user *buf, size_t len, loff_t *off) { + unsigned char mtype; + unsigned char *buffer; + unsigned long _rv; + int block, rv = 0; + + if (len > ESP32_MTU) return -EINVAL; + + buffer = esp32_alloc(); + if (buffer == NULL) return -ENOMEM; + + _rv = copy_from_user(buffer + ESP32_SIZE_HDR, buf, len); + if (_rv) rv = -EFAULT; + if (rv) { + esp32_free(buffer); + return rv; + } + + block = !(file->f_flags & O_NONBLOCK); + mtype = (unsigned char)(file->private_data); + buffer[0] = mtype; + buffer[1] = len >> 8; + buffer[2] = len & 0xFF; + + rv = esp32_send(buffer, block); + if (rv) return rv; + + return len; +} + +static int esp32_probe(struct spi_device *spi) { + dev_t _dev; + struct device *dev_ptr; + const char *fmt; + int i, rv, major, minor; + const struct of_device_id *match; + + match = of_match_device(of_match_ptr(esp32_of_match), &spi->dev); + if (match == NULL) return -ENODEV; + + esp32_spi_dev = spi; + + gpio_rts = gpiod_get(&spi->dev, "rts", GPIOD_IN); + if (IS_ERR(gpio_rts)) goto err_gpiod_get_rts; + + irq_rts = gpiod_to_irq(gpio_rts); + if (irq_rts < 0) goto err_gpiod_get_rts; + + gpio_cts = gpiod_get(&spi->dev, "cts", GPIOD_IN); + if (IS_ERR(gpio_cts) || gpiod_cansleep(gpio_rts)) goto err_gpiod_get_cts; + + irq_cts = gpiod_to_irq(gpio_cts); + if (irq_cts < 0) goto err_gpiod_get_cts; + + rv = request_irq(irq_rts, rts_irq_handler, IRQF_TRIGGER_FALLING, "esp32_rts", NULL); + if (rv < 0) goto err_request_irq_rts; + + rv = request_irq(irq_cts, cts_irq_handler, IRQF_TRIGGER_FALLING, "esp32_cts", NULL); + if (rv < 0) goto err_request_irq_cts; + + kthd = kthread_run(xchg_thd, NULL, "esp32"); + if (IS_ERR(kthd)) goto err_kthread_run; + + rv = alloc_chrdev_region(&dev, 0, ESP32_MAX_MTYPE, "esp32"); + if (rv) goto err_alloc_chrdev; + + cdev_init(&dev_cdev, &dev_fops); + + rv = cdev_add(&dev_cdev, dev, ESP32_MAX_MTYPE); + if (rv) goto err_cdev_add; + + dev_class = class_create("esp32"); + if (IS_ERR(dev_class)) goto err_class_create; + + major = MAJOR(dev); + minor = MINOR(dev); + for (i=0; i<ESP32_MAX_MTYPE; i++) { + _dev = MKDEV(major, minor + i); + fmt = i & ESP32_MTYPE_FLAG_BRIDGE ? "fe310.%d" : "esp32.%d"; + dev_ptr = device_create(dev_class, NULL, _dev, NULL, fmt, i & ~ESP32_MTYPE_FLAG_BRIDGE); + if (IS_ERR(dev_ptr)) goto err_device_create; + } + + return 0; + +err_device_create: + for (i--; i>=0; i--) { + _dev = MKDEV(major, minor + i); + device_destroy(dev_class, _dev); + } + class_destroy(dev_class); + +err_class_create: + cdev_del(&dev_cdev); + +err_cdev_add: + unregister_chrdev_region(dev, ESP32_MAX_MTYPE); + +err_alloc_chrdev: + kthread_stop(kthd); + +err_kthread_run: + free_irq(irq_cts, NULL); + +err_request_irq_cts: + free_irq(irq_rts, NULL); + +err_request_irq_rts: +err_gpiod_get_cts: + if (!IS_ERR(gpio_cts)) gpiod_put(gpio_rts); + +err_gpiod_get_rts: + if (!IS_ERR(gpio_rts)) gpiod_put(gpio_rts); + return -1; +} + +static void esp32_remove(struct spi_device *spi) { + unsigned char *buffer; + dev_t _dev; + int i, major, minor; + + major = MAJOR(dev); + minor = MINOR(dev); + for (i=0; i<ESP32_MAX_MTYPE; i++) { + _dev = MKDEV(major, minor + i); + device_destroy(dev_class, _dev); + } + class_destroy(dev_class); + cdev_del(&dev_cdev); + unregister_chrdev_region(dev, ESP32_MAX_MTYPE); + kthread_stop(kthd); + free_irq(irq_cts, NULL); + free_irq(irq_rts, NULL); + gpiod_put(gpio_cts); + gpiod_put(gpio_rts); + + /* flush remaining buffers */ + spin_lock(&snd_msgq.lock); + while ((buffer = msgq_pop(&snd_msgq))) { + esp32_free(buffer); + } + msgq_destroy(&snd_msgq); + spin_unlock(&snd_msgq.lock); + for (i=0; i<ESP32_MAX_MTYPE; i++) { + spin_lock(&rcv_msgq[i].lock); + while ((buffer = msgq_pop(&rcv_msgq[i]))) { + esp32_free(buffer); + } + msgq_destroy(&rcv_msgq[i]); + spin_unlock(&rcv_msgq[i].lock); + } +} + +static int __init esp32_init(void) { + int i, rv; + + rv = msgq_create(&snd_msgq, MSGQ_SIZE_SND); + if (rv) return rv; + + for (i=0; i<ESP32_MAX_MTYPE; i++) { + rv = msgq_create(&rcv_msgq[i], MSGQ_SIZE_RCV); + if (rv) goto esp32_init_err; + } + + rv = spi_register_driver(&esp32_spi_driver); + if (rv) goto esp32_init_err; + + return 0; + +esp32_init_err: + for (i--; i>=0; i--) { + msgq_destroy(&rcv_msgq[i]); + } + msgq_destroy(&snd_msgq); + return rv; +} + +static void __exit esp32_exit(void) { + spi_unregister_driver(&esp32_spi_driver); +} + +module_init(esp32_init); +module_exit(esp32_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Uros Majstorovic <majstor@majstor.org>"); +MODULE_DESCRIPTION("esp32 SPI driver"); +MODULE_VERSION("1.0"); + +int esp32_send(unsigned char *buffer, int block) { + int rv, wake_xchg; + + spin_lock(&snd_msgq.lock); + do { + wake_xchg = 0; + rv = msgq_push(&snd_msgq, buffer); + if (!rv && (msgq_len(&snd_msgq) == 1)) { + wake_xchg = 1; + } + if (block && rv) { + int _rv; + + _rv = wait_event_interruptible_lock(snd_msgq.wq, msgq_len(&snd_msgq) != msgq_size(&snd_msgq), snd_msgq.lock); + if (_rv) { + rv = _rv; + break; + } + } + } while (block && rv); + spin_unlock(&snd_msgq.lock); + + if (wake_xchg) wake_up(&xchg_wq); + if (rv) esp32_free(buffer); + + return rv; +} + +int esp32_receive(unsigned char mtype, int block, unsigned char **buffer) { + int rv; + + *buffer = NULL; + if (mtype >= ESP32_MAX_MTYPE) return -EINVAL; + + rv = 0; + spin_lock(&rcv_msgq[mtype].lock); + *buffer = msgq_pop(&rcv_msgq[mtype]); + while (block && (*buffer == NULL)) { + rv = wait_event_interruptible_lock(rcv_msgq[mtype].wq, msgq_len(&rcv_msgq[mtype]) != 0, rcv_msgq[mtype].lock); + if (rv) break; + + *buffer = msgq_pop(&rcv_msgq[mtype]); + } + spin_unlock(&rcv_msgq[mtype].lock); + + if (!rv && (*buffer == NULL)) rv = -EAGAIN; + return rv; +} + +unsigned char *esp32_alloc(void) { + return kmalloc(ESP32_SIZE_BUF, GFP_KERNEL); +} + +void esp32_free(unsigned char *buffer) { + kfree(buffer); +} diff --git a/yocto/esp32mod/esp32.h b/yocto/esp32mod/esp32.h new file mode 100755 index 0000000..b00cc4f --- /dev/null +++ b/yocto/esp32mod/esp32.h @@ -0,0 +1,16 @@ +#define ESP32_MTU 1500 + +#define ESP32_MAX_MTYPE 16 + +#define ESP32_MTYPE_SLEEP 0x20 /* does not have net handler */ + +#define ESP32_MTYPE_FLAG_ONEW 0x80 +#define ESP32_MTYPE_FLAG_REPL 0x40 +#define ESP32_MTYPE_FLAG_BRIDGE 0x08 + +#define ESP32_MTYPE_MASK 0x0F + +int esp32_send(unsigned char *buffer, int block); +int esp32_receive(unsigned char msg_type, int block, unsigned char **buffer); +unsigned char *esp32_alloc(void); +void esp32_free(unsigned char *buffer);
\ No newline at end of file diff --git a/yocto/esp32mod/msgq.c b/yocto/esp32mod/msgq.c new file mode 100755 index 0000000..77ece4b --- /dev/null +++ b/yocto/esp32mod/msgq.c @@ -0,0 +1,50 @@ +#include <linux/module.h> + +#include "msgq.h" + +#define IDX_MASK(IDX, SIZE) ((IDX) & ((SIZE) - 1)) + +int msgq_create(MSGQueue *msgq, uint16_t size) { + unsigned char **array; + + array = kmalloc(size * sizeof(unsigned char *), GFP_KERNEL); + if (array == NULL) return -ENOMEM; + + msgq->idx_r = 0; + msgq->idx_w = 0; + msgq->size = size; + msgq->array = array; + spin_lock_init(&msgq->lock); + init_waitqueue_head(&msgq->wq); + + return 0; +} + +void msgq_destroy(MSGQueue *msgq) { + kfree(msgq->array); + msgq->idx_r = 0; + msgq->idx_w = 0; + msgq->size = 0; + msgq->array = NULL; +} + +uint16_t msgq_len(MSGQueue *msgq) { + return (uint16_t)(msgq->idx_w - msgq->idx_r); +} + +uint16_t msgq_size(MSGQueue *msgq) { + return msgq->size; +} + +int msgq_push(MSGQueue *msgq, unsigned char *buffer) { + if ((uint16_t)(msgq->idx_w - msgq->idx_r) == msgq->size) return -EAGAIN; + + msgq->array[IDX_MASK(msgq->idx_w++, msgq->size)] = buffer; + return 0; +} + +unsigned char *msgq_pop(MSGQueue *msgq) { + if (msgq->idx_r == msgq->idx_w) return NULL; + + return msgq->array[IDX_MASK(msgq->idx_r++, msgq->size)]; +} diff --git a/yocto/esp32mod/msgq.h b/yocto/esp32mod/msgq.h new file mode 100755 index 0000000..f0d82f4 --- /dev/null +++ b/yocto/esp32mod/msgq.h @@ -0,0 +1,15 @@ +typedef struct MSGQueue { + uint16_t idx_r; + uint16_t idx_w; + uint16_t size; + unsigned char **array; + spinlock_t lock; + wait_queue_head_t wq; +} MSGQueue; + +int msgq_create(MSGQueue *msgq, uint16_t size); +void msgq_destroy(MSGQueue *msgq); +uint16_t msgq_len(MSGQueue *msgq); +uint16_t msgq_size(MSGQueue *msgq); +int msgq_push(MSGQueue *msgq, unsigned char *buffer); +unsigned char *msgq_pop(MSGQueue *msgq); |
