Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ClusterM/rc-transceiver.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com>2022-04-10 19:35:29 +0300
committerAlexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com>2022-04-10 19:35:29 +0300
commit8f5b64e68f33a2079d99615e1e0653e482a2c303 (patch)
treed1f31011b2add1bdd44f8bdda98bad81f5351152 /linux_kernel_module
First commit
Diffstat (limited to 'linux_kernel_module')
-rw-r--r--linux_kernel_module/Makefile40
-rw-r--r--linux_kernel_module/src/Makefile1
-rw-r--r--linux_kernel_module/src/rc-transceiver.c483
-rw-r--r--linux_kernel_module/src/rc-transceiver.h67
4 files changed, 591 insertions, 0 deletions
diff --git a/linux_kernel_module/Makefile b/linux_kernel_module/Makefile
new file mode 100644
index 0000000..ddbd9b7
--- /dev/null
+++ b/linux_kernel_module/Makefile
@@ -0,0 +1,40 @@
+include $(TOPDIR)/rules.mk
+include $(INCLUDE_DIR)/kernel.mk
+
+# name
+PKG_NAME:=rc-transceiver
+# version of what we are downloading
+PKG_VERSION:=1.0
+# version of this makefile
+PKG_RELEASE:=0
+
+REMOTE_DIR:=root@10.13.1.43:/lib/modules/4.14.81/
+
+PKG_BUILD_DIR:=$(KERNEL_BUILD_DIR)/$(PKG_NAME)
+#PKG_BUILD_DIR:=$(TOPDIR)/$(PKG_NAME)
+PKG_CHECK_FORMAT_SECURITY:=0
+
+include $(INCLUDE_DIR)/package.mk
+
+define KernelPackage/$(PKG_NAME)
+ SUBMENU:=Other modules
+ TITLE:=RC-transceiver driver
+ FILES:= $(PKG_BUILD_DIR)/rc-transceiver.ko
+endef
+
+define KernelPackage/$(PKG_NAME)/description
+ RC-transceiver driver.
+endef
+
+MAKE_OPTS:= \
+ $(KERNEL_MAKE_FLAGS) \
+ SUBDIRS=$(PKG_BUILD_DIR)
+
+define Build/Compile
+ $(MAKE) -C "$(LINUX_DIR)" \
+ $(MAKE_OPTS) \
+ modules
+ scp $(PKG_BUILD_DIR)/$(PKG_NAME).ko $(REMOTE_DIR)
+endef
+
+$(eval $(call KernelPackage,$(PKG_NAME)))
diff --git a/linux_kernel_module/src/Makefile b/linux_kernel_module/src/Makefile
new file mode 100644
index 0000000..9dfb172
--- /dev/null
+++ b/linux_kernel_module/src/Makefile
@@ -0,0 +1 @@
+obj-m := rc-transceiver.o
diff --git a/linux_kernel_module/src/rc-transceiver.c b/linux_kernel_module/src/rc-transceiver.c
new file mode 100644
index 0000000..7313615
--- /dev/null
+++ b/linux_kernel_module/src/rc-transceiver.c
@@ -0,0 +1,483 @@
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/ktime.h>
+#include <linux/hrtimer.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+#include <linux/string.h>
+#include "rc-transceiver.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alexey 'Cluster' Avdyukhin");
+MODULE_DESCRIPTION("rc_transceiver driver for Omega2");
+MODULE_VERSION("1.0");
+
+static int pwm_channel = -1;
+static int rx_pin = -1;
+
+module_param(rx_pin, int, S_IRUGO);
+MODULE_PARM_DESC(rx_pin,"RC receiver pin number");
+module_param(pwm_channel, int, S_IRUGO);
+MODULE_PARM_DESC(pwm_channel,"RC transmitter PWM channel number");
+
+static int majorNumber = -1;
+static struct class* rc_transceiver_class = NULL;
+static struct device* rc_device = NULL;
+static int bus_number_opens = 0;
+static struct file* opened_files[MAX_OPENED_FILES];
+
+static void* pwm_regs = NULL;
+static struct hrtimer* tx_timer = NULL;
+static rctime_t tx_buffer[BUFFER_SIZE];
+static u16 tx_buffer_size = 0;
+static u16 tx_buffer_pos = 0;
+static DEFINE_MUTEX(tx_mutex);
+static u8 tx_active = 0;
+
+static struct gpio_desc* rx_pin_desc = NULL;
+static u32 rx_irq_number = 0xffff;
+static struct hrtimer* rx_timer = NULL;
+static u64 rx_start_time = 0;
+static rctime_t rx_buffer[BUFFER_SIZE];
+static u16 rx_buffer_pos = 0;
+static DECLARE_WAIT_QUEUE_HEAD(rx_wq);
+
+// The prototype functions for the character driver -- must come before the struct definition
+static int dev_open(struct inode *, struct file *);
+static int dev_release(struct inode *, struct file *);
+static ssize_t dev_read(struct file *, char *, size_t, loff_t *);
+static ssize_t dev_write(struct file *, const char *, size_t, loff_t *);
+// static loff_t dev_llseek(struct file *file,loff_t offset, int orig);
+
+static struct file_operations fops =
+{
+ .open = dev_open,
+ .read = dev_read,
+ .write = dev_write,
+ .release = dev_release,
+ .llseek = 0 //dev_llseek
+};
+
+/* IRQ fired every rising/falling edge of receiver pin */
+static irq_handler_t rx_irq_handler(unsigned int irq,
+ void *dev_id, struct pt_regs *regs)
+{
+ u64 now = ktime_to_us(ktime_get_boottime());
+ rctime_t time_since_first;
+
+ // ignore signals while transmitting
+ if (tx_active)
+ return (irq_handler_t) IRQ_HANDLED;
+
+ // limit to buffer size
+ if (rx_buffer_pos >= BUFFER_SIZE)
+ return (irq_handler_t) IRQ_HANDLED;
+
+ if (
+ (((rx_buffer_pos % 2) == 0) && gpiod_get_value(rx_pin_desc)) // must be low
+ || (((rx_buffer_pos % 2) == 1) && !gpiod_get_value(rx_pin_desc)) // must be high
+ )
+ return (irq_handler_t) IRQ_HANDLED;
+
+ if (rx_buffer_pos == 0) {
+ rx_start_time = now;
+ }
+ // store time since first impulse
+ time_since_first = now - rx_start_time;
+ // filter
+ if ((rx_buffer_pos > 0) && (time_since_first - rx_buffer[rx_buffer_pos - 1] < RX_FILTER_MIN_PULSE_US)) {
+ // noise
+ if (rx_buffer_pos >= 2) {
+ rx_buffer[rx_buffer_pos - 2] = time_since_first;
+ rx_buffer_pos -= 1;
+ } else {
+ rx_buffer_pos = 0;
+ }
+ } else {
+ // stable signal
+ rx_buffer[rx_buffer_pos] = time_since_first;
+ rx_buffer_pos++;
+ // schedule timeout timer
+ if (rx_timer) hrtimer_try_to_cancel(rx_timer);
+ hrtimer_start(rx_timer, ktime_set(0, RX_TIMEOUT_USEC * 1000UL), HRTIMER_MODE_REL);
+ }
+
+ return (irq_handler_t) IRQ_HANDLED;
+}
+
+/* RX timeout timer callback */
+static enum hrtimer_restart rx_timeout_callback(struct hrtimer *timer)
+{
+ int i;
+ rcfile_t *rcf;
+ // transmition finished
+ if (rx_buffer_pos > RX_FILTER_MIN_COUNT) {
+ // convert relative timings to absolute
+ for (i = 1; i < rx_buffer_pos; i++) {
+ rx_buffer[i - 1] = rx_buffer[i] - rx_buffer[i - 1];
+ }
+ rx_buffer_pos--;
+ // send data to clients
+ for (i = 0; i < bus_number_opens; i++) {
+ rcf = (rcfile_t*)opened_files[i]->private_data;
+ if (!rcf->rx_pending) {
+ memcpy(rcf->rx_buffer, rx_buffer, rx_buffer_pos * sizeof(rctime_t));
+ rcf->rx_size = rx_buffer_pos;
+ rcf->rx_pos_nibbles = 0;
+ rcf->rx_pending = 1;
+ wake_up_interruptible(&rx_wq);
+ }
+ }
+ }
+ rx_buffer_pos = 0;
+ rx_start_time = 0;
+ return HRTIMER_NORESTART;
+}
+
+/* Function to schedule TX timer */
+static void set_tx_timer(void)
+{
+ uint32_t enable;
+ uint32_t reg_offset = 0x40 * pwm_channel;
+ uint16_t duration = 40000000 / TX_CARRIER_FREQ;
+ uint16_t duration_h = duration / 2;
+ uint16_t duration_l = duration / 2;
+
+ if (tx_timer) hrtimer_try_to_cancel(tx_timer);
+
+ enable = REG_READ(PWM_ENABLE);
+ enable &= ~((uint32_t)(1 << pwm_channel));
+ REG_WRITE(PWM_ENABLE, enable);
+
+ if (tx_buffer_pos >= tx_buffer_size) {
+ tx_active = 0;
+ mutex_unlock(&tx_mutex);
+ return; // done
+ }
+
+ if ((tx_buffer_pos % 2) == 0) {
+ REG_WRITE(PWM0_CON + reg_offset, 0x7000 | CLKSEL_40MHZ | CLKDIV_1);
+ REG_WRITE(PWM0_HDURATION + reg_offset, duration_h - 1);
+ REG_WRITE(PWM0_LDURATION + reg_offset, duration_l - 1);
+ REG_WRITE(PWM0_GDURATION + reg_offset, (duration_h + duration_l) / 2 - 1);
+ REG_WRITE(PWM0_SEND_DATA0 + reg_offset, 0x55555555);
+ REG_WRITE(PWM0_SEND_DATA1 + reg_offset, 0x55555555);
+ REG_WRITE(PWM0_WAVE_NUM + reg_offset, 0);
+
+ enable |= 1 << pwm_channel;
+ REG_WRITE(PWM_ENABLE, enable);
+ }
+
+ hrtimer_start(tx_timer, ktime_set(0, tx_buffer[tx_buffer_pos] * 1000UL), HRTIMER_MODE_REL);
+ tx_buffer_pos++;
+}
+
+/* TX timer callback */
+static enum hrtimer_restart tx_callback(struct hrtimer *timer)
+{
+ set_tx_timer();
+ return HRTIMER_NORESTART;
+}
+
+/* Function to start transmit */
+static void transmit(rctime_t *seq, int len)
+{
+ memcpy(tx_buffer, seq, len * sizeof(rctime_t));
+ tx_buffer_size = len;
+ tx_buffer_pos = 0;
+ if (len) set_tx_timer();
+}
+
+/* Function to free all resources */
+static void rc_transceiver_free(void)
+{
+ if (!IS_ERR_OR_NULL(rc_transceiver_class)) {
+ // remove the device
+ device_destroy(rc_transceiver_class, MKDEV(majorNumber, DEV_MINOR));
+ // unregister the device class
+ class_unregister(rc_transceiver_class);
+ // remove the device class
+ class_destroy(rc_transceiver_class);
+ }
+ // unregister the major number
+ if (majorNumber >= 0) {
+ unregister_chrdev(majorNumber, DEVICE_BUS);
+ }
+
+ if (tx_timer) {
+ hrtimer_try_to_cancel(tx_timer);
+ kfree(tx_timer);
+ }
+
+ if (rx_timer) {
+ hrtimer_try_to_cancel(rx_timer);
+ kfree(rx_timer);
+ }
+
+ // unmap registers
+ if (pwm_regs != NULL)
+ iounmap(pwm_regs);
+
+ // free IRQ
+ if (rx_irq_number != 0xffff)
+ free_irq(rx_irq_number, NULL);
+ // free RX pin
+ if (!IS_ERR_OR_NULL(rx_pin_desc))
+ gpiod_put(rx_pin_desc);
+}
+
+/* Function to init the module */
+static __init int rc_transceiver_init(void)
+{
+ int r;
+
+ if ((rx_pin < 0) && (pwm_channel < 0)) {
+ printk(KERN_ERR "rc-transceiver: You must specify either rx_pin or pwm_channel\n");
+ return -1;
+ }
+
+ // register character device and request major number
+ majorNumber = register_chrdev(0, DEVICE_BUS, &fops);
+ if (majorNumber < 0) {
+ printk(KERN_ERR "rc-transceiver: failed to register a major number\n");
+ rc_transceiver_free();
+ return -1;
+ }
+ // register the device class
+ rc_transceiver_class = class_create(THIS_MODULE, CLASS_NAME);
+ if (IS_ERR(rc_transceiver_class)) {
+ printk(KERN_ERR "rc-transceiver: failed to register device class: %ld\n", PTR_ERR(rc_transceiver_class));
+ rc_transceiver_free();
+ return -1;
+ }
+ // register the device driver
+ rc_device = device_create(rc_transceiver_class, NULL, MKDEV(majorNumber, DEV_MINOR), NULL, DEVICE_NAME);
+ if (IS_ERR(rc_device)) {
+ printk(KERN_ERR "rc-transceiver: failed to create the TX device: %ld\n", PTR_ERR(rc_device));
+ rc_transceiver_free();
+ return -1;
+ }
+
+ if (rx_pin >= 0) {
+ // prepare pin for the receiver
+ rx_pin_desc = gpio_to_desc(rx_pin);
+ if (IS_ERR(rx_pin_desc)) {
+ printk(KERN_ERR "rc-transceiver: rx_pin gpiod_request error: %ld\n", PTR_ERR(rx_pin_desc));
+ rc_transceiver_free();
+ return -1;
+ }
+ // input
+ gpiod_direction_input(rx_pin_desc);
+ // prepare IRQ for the receiver
+ rx_irq_number = gpiod_to_irq(rx_pin_desc);
+ r = request_irq(
+ // The interrupt number requested
+ rx_irq_number,
+ // The pointer to the handler function below */
+ (irq_handler_t) rx_irq_handler,
+ /* Interrupt on falling edge */
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ /* Used in /proc/interrupts to identify the owner */
+ "rc_handler",
+ NULL);
+ if (r) {
+ printk(KERN_ERR "transceiver: rx_pin request_irq error\n");
+ rc_transceiver_free();
+ return -1;
+ }
+ // allocate and init timer for receiver
+ rx_timer = kzalloc(sizeof(struct hrtimer), GFP_KERNEL);
+ if (!rx_timer) {
+ printk(KERN_ERR "rc-transceiver: can't allocate memory for timer\n");
+ rc_transceiver_free();
+ return -1;
+ }
+ hrtimer_init(rx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ rx_timer->function = rx_timeout_callback;
+ }
+
+ if (pwm_channel >= 0) {
+ // PWM registers for transmitter
+ pwm_regs = ioremap_nocache(PWM_BASE, PWM_SIZE);
+ if (pwm_regs == NULL) {
+ printk(KERN_ERR "rc-transceiver: failed to map physical memory\n");
+ rc_transceiver_free();
+ return -1;
+ }
+ // allocate and init timer for transmitter
+ tx_timer = kzalloc(sizeof(struct hrtimer), GFP_KERNEL);
+ if (!tx_timer) {
+ printk(KERN_ERR "rc-transceiver: can't allocate memory for timer\n");
+ rc_transceiver_free();
+ return -1;
+ }
+ hrtimer_init(tx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ tx_timer->function = tx_callback;
+ }
+
+ printk(KERN_INFO "rc-transceiver: driver started\n");
+ return 0;
+}
+
+/* Function to unload module */
+static void __exit rc_transceiver_exit(void)
+{
+ rc_transceiver_free();
+ printk(KERN_INFO "rc-transceiver: driver stopped\n");
+}
+
+static int dev_open(struct inode *inodep, struct file *filep)
+{
+ rcfile_t *rcf;
+ if (bus_number_opens >= MAX_OPENED_FILES)
+ return -EMFILE;
+ filep->private_data = kzalloc(sizeof(rcfile_t), GFP_KERNEL);
+ if (!filep->private_data)
+ return -ENOMEM;
+ rcf = (rcfile_t*)filep->private_data;
+ rcf->id = bus_number_opens;
+ rcf->tx_pos_nibbles = 0;
+ rcf->rx_size = 0;
+ rcf->rx_pos_nibbles = 0;
+ rcf->rx_pending = 0;
+ opened_files[bus_number_opens] = filep;
+ bus_number_opens++;
+ return 0;
+}
+
+static ssize_t dev_read(struct file *filep, char *buffer, size_t len, loff_t *offset)
+{
+ int lpos;
+ char c;
+ u8 b;
+ ssize_t r = 0;
+ rcfile_t *rcf = (rcfile_t*)filep->private_data;
+
+ // no data yet
+ if (!rcf->rx_pending) {
+ if (filep->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ if (wait_event_interruptible(rx_wq, rcf->rx_pending))
+ return -ERESTARTSYS;
+ }
+
+ for (; rcf->rx_pending && (r < len); r++, rcf->rx_pos_nibbles++, buffer++) {
+ lpos = rcf->rx_pos_nibbles / 4;
+
+ if (lpos >= rcf->rx_size) {
+ c = '\n';
+ // reset
+ rcf->rx_size = 0;
+ rcf->rx_pos_nibbles = 0;
+ rcf->rx_pending = 0;
+ } else {
+ switch (rcf->rx_pos_nibbles % 4) {
+ case 0:
+ b = (rcf->rx_buffer[lpos] >> 4) & 0xF;
+ break;
+ case 1:
+ b = rcf->rx_buffer[lpos] & 0xF;
+ break;
+ case 2:
+ b = (rcf->rx_buffer[lpos] >> 12) & 0xF;
+ break;
+ case 3:
+ b = (rcf->rx_buffer[lpos] >> 8) & 0xF;
+ break;
+ }
+ if (b < 10) {
+ c = '0' + b;
+ } else {
+ c = 'a' - 10 + b;
+ }
+ }
+ put_user(c, buffer);
+ }
+
+ return r;
+}
+
+
+static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset)
+{
+ ssize_t r = 0;
+ int lpos;
+ char c;
+ u8 b;
+ rcfile_t *rcf = (rcfile_t*)filep->private_data;
+
+ for (r = 0; r < len; r++, buffer++) {
+ lpos = rcf->tx_pos_nibbles / 4;
+
+ if (get_user(c, buffer)) {
+ return -EFAULT;
+ }
+
+ if (c == '\r' || c == '\n') {
+ if (lpos > 0) {
+ if ((lpos % 2) == 1) {
+ rcf->tx_buffer[lpos] = TX_FINAL_GAP_USEC;
+ rcf->tx_pos_nibbles += 4;
+ } else {
+ rcf->tx_buffer[lpos - 1] = TX_FINAL_GAP_USEC;
+ }
+ if (mutex_lock_interruptible(&tx_mutex)) {
+ return -ERESTARTSYS;
+ }
+ tx_active = 1;
+ transmit(rcf->tx_buffer, lpos);
+ }
+ rcf->tx_pos_nibbles = 0;
+ continue;
+ }
+
+ // limit to buffer size
+ if (lpos >= BUFFER_SIZE) {
+ if (r) return r;
+ return -EFAULT;
+ }
+
+ b = 0;
+ if (c >= '0' && c <= '9')
+ b = c - '0';
+ else if (c >= 'a' && c <= 'f')
+ b = c + 10 - 'a';
+ else if (c >= 'A' && c <= 'F')
+ b = c + 10 - 'A';
+
+ switch (rcf->tx_pos_nibbles % 4) {
+ case 0:
+ rcf->tx_buffer[lpos] = b << 4;
+ break;
+ case 1:
+ rcf->tx_buffer[lpos] = (rcf->tx_buffer[lpos] & 0xFFF0) | b;
+ break;
+ case 2:
+ rcf->tx_buffer[lpos] = (rcf->tx_buffer[lpos] & 0x0FFF) | (b << 12);
+ break;
+ case 3:
+ rcf->tx_buffer[lpos] = (rcf->tx_buffer[lpos] & 0xF0FF) | (b << 8);
+ break;
+ }
+ rcf->tx_pos_nibbles++;
+ }
+ return r;
+}
+
+static int dev_release(struct inode *inodep, struct file *filep)
+{
+ int id;
+ bus_number_opens--;
+ id = ((rcfile_t*)filep->private_data)->id;
+ opened_files[id] = opened_files[bus_number_opens];
+ ((rcfile_t*)opened_files[id]->private_data)->id = id;
+ kfree(filep->private_data);
+ return 0;
+}
+
+module_init(rc_transceiver_init);
+module_exit(rc_transceiver_exit);
diff --git a/linux_kernel_module/src/rc-transceiver.h b/linux_kernel_module/src/rc-transceiver.h
new file mode 100644
index 0000000..4832fe7
--- /dev/null
+++ b/linux_kernel_module/src/rc-transceiver.h
@@ -0,0 +1,67 @@
+#ifndef _RC_TRANSCIEIVER_H_
+#define _RC_TRANSCIEIVER_H_
+
+#define CLASS_NAME "rc"
+#define DEVICE_BUS "rc"
+#define DEVICE_NAME "rc"
+#define DEV_MINOR 0
+
+// Interval between transmissions
+#define TX_FINAL_GAP_USEC 50000
+// Carrier frequency for transmitter
+#define TX_CARRIER_FREQ 36000
+// Recevier gap time between frames
+#define RX_TIMEOUT_USEC 10000
+// Receiver noise filter: minimum frame length (3 = 1 signal + 1 gap +1 signal)
+#define RX_FILTER_MIN_COUNT 3
+// Receiver noise filter: minimum signal/gap length
+#define RX_FILTER_MIN_PULSE_US 50
+// Mamimum count of signal and gaps interval
+#define BUFFER_SIZE 256
+// How many pseudo-files can be opened
+#define MAX_OPENED_FILES 32
+
+#define PWM_BASE 0x10005000
+#define PWM_SIZE 32 + 0x40 * 4
+#define PWM_ENABLE 0x00000000 // PWM Enable register
+#define PWM0_CON 0x00000010 // PWM0 Control register
+#define PWM0_HDURATION 0x00000014 // PWM0 High Duration register
+#define PWM0_LDURATION 0x00000018 // PWM0 Low Duration register
+#define PWM0_GDURATION 0x0000001C // PWM0 Guard Duration register
+#define PWM0_SEND_DATA0 0x00000030 // PWM0 Send Data0 register
+#define PWM0_SEND_DATA1 0x00000034 // PWM0 Send Data1 register
+#define PWM0_WAVE_NUM 0x00000038 // PWM0 Wave Number register
+#define PWM0_DATA_WIDTH 0x0000003C // PWM0 Data Width register
+#define PWM0_THRESH 0x00000040 // PWM0 Thresh register
+#define PWM0_SEND_WAVENUM 0x00000044 // PWM0 Send Wave Number register
+
+#define CLKSEL_100KHZ 0b00000000
+#define CLKSEL_40MHZ 0b00001000
+
+#define CLKDIV_1 0b00000000
+#define CLKDIV_2 0b00000001
+#define CLKDIV_4 0b00000010
+#define CLKDIV_8 0b00000011
+#define CLKDIV_16 0b00000100
+#define CLKDIV_32 0b00000101
+#define CLKDIV_64 0b00000110
+#define CLKDIV_128 0b00000111
+
+#define REG_WRITE(addr, value) iowrite32(value, pwm_regs + addr)
+#define REG_READ(addr) ioread32(pwm_regs + addr)
+
+typedef u32 rctime_t;
+
+struct rcfile {
+ u16 id;
+ rctime_t tx_buffer[BUFFER_SIZE];
+ u16 tx_pos_nibbles;
+ rctime_t rx_buffer[BUFFER_SIZE];
+ u16 rx_size;
+ u16 rx_pos_nibbles;
+ u8 rx_pending;
+};
+
+typedef struct rcfile rcfile_t;
+
+#endif