diff --git a/utils/gl-mifi-mcu/Makefile b/utils/gl-mifi-mcu/Makefile new file mode 100755 index 000000000..08183d91e --- /dev/null +++ b/utils/gl-mifi-mcu/Makefile @@ -0,0 +1,36 @@ +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_NAME:=gl-mifi-mcu +PKG_VERSION:=1 +PKG_RELEASE:=1 + +PKG_MAINTAINER:=Nuno Goncalves +PKG_LICENSE:=GPL-3.0-or-later + +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/gl-mifi-mcu + SUBMENU:=Hardware Monitoring Support + TITLE:=GL.iNet GL-MiFI Power monitoring support + AUTOLOAD:=$(call AutoLoad,60,gl-mifi-mcu) + FILES:=$(PKG_BUILD_DIR)/gl-mifi-mcu.ko +endef + +define KernelPackage/gl-mifi-mcu/description + Interfaces with GL-MiFI Power monitoring MCU with a soft UART + and provides Battery SOC, Temperature and charging data at + /proc/gl_mifi_mcu. + This feature is supported from GL-MiFi PCB revision v2.6.2. + The content of /proc/gl_mifi_mcu is JSON as received from the + UART and will frequenty contain corrupted data due to soft UART + unreliability. User application must validate the data. +endef + +define Build/Compile + $(KERNEL_MAKE) \ + M="$(PKG_BUILD_DIR)" \ + modules +endef + +$(eval $(call KernelPackage,gl-mifi-mcu)) diff --git a/utils/gl-mifi-mcu/src/Makefile b/utils/gl-mifi-mcu/src/Makefile new file mode 100755 index 000000000..bce424b1b --- /dev/null +++ b/utils/gl-mifi-mcu/src/Makefile @@ -0,0 +1,4 @@ +obj-m += gl-mifi-mcu.o + +gl-mifi-mcu-objs := module.o + diff --git a/utils/gl-mifi-mcu/src/module.c b/utils/gl-mifi-mcu/src/module.c new file mode 100755 index 000000000..39a83267d --- /dev/null +++ b/utils/gl-mifi-mcu/src/module.c @@ -0,0 +1,200 @@ +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Nuno Goncalves"); +MODULE_DESCRIPTION("GL-MiFi power monitoring MCU interface"); +MODULE_VERSION("0.1"); + +static int gpio_tx = 19; +static int gpio_rx = 8; +static int baudrate = 1200; +static int query_interval_sec = 4; + +static struct hrtimer timer_tx; +static struct hrtimer timer_rx; +static ktime_t period; +static int rx_bit_index = -1; + +static unsigned read_buf_ready = 0; +static unsigned read_buf_size = 0; +static char read_buf[2][64] = {{0},{0}}; + +static int proc_show(struct seq_file *m, void *v) +{ + seq_printf(m, "%s\n", read_buf[read_buf_ready]); + return 0; +} + +static int proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, proc_show, NULL); +} + +static const struct file_operations hello_proc_fops = { + .owner = THIS_MODULE, + .open = proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static irq_handler_t handle_rx_start(unsigned int irq, void* device, struct pt_regs* registers) +{ + if (rx_bit_index == -1) + { + hrtimer_start(&timer_rx, ktime_set(0, period / 2), HRTIMER_MODE_REL); + } + return (irq_handler_t) IRQ_HANDLED; +} + +static enum hrtimer_restart handle_tx(struct hrtimer* timer) +{ + ktime_t current_time = ktime_get(); + const unsigned char character = 'g'; + static int bit_index = -1; + + // Start bit. + if (bit_index == -1) + { + gpio_set_value(gpio_tx, 0); + bit_index++; + } + + // Data bits. + else if (0 <= bit_index && bit_index < 8) + { + gpio_set_value(gpio_tx, 1 & (character >> bit_index)); + bit_index++; + } + + // Stop bit. + else if (bit_index == 8) + { + gpio_set_value(gpio_tx, 1); + bit_index = -1; + } + + hrtimer_forward(&timer_tx, current_time, bit_index == 8 + ? ktime_set(query_interval_sec, 0) //wait for next query cycle + : period); //wait for next bit period + + return HRTIMER_RESTART; +} + +void receive_character(unsigned char character) +{ + if(character == '{') + read_buf_size = 0; + + if(read_buf_size < (sizeof(read_buf[0])-1) || character == '}') + { + read_buf[!read_buf_ready][read_buf_size++] = character; + if(character == '}') + { + read_buf[!read_buf_ready][read_buf_size] = '\0'; + read_buf_ready = !read_buf_ready; + read_buf_size = 0; + } + } +} + +static enum hrtimer_restart handle_rx(struct hrtimer* timer) +{ + ktime_t current_time = ktime_get(); + static unsigned int character = 0; + int bit_value = gpio_get_value(gpio_rx); + enum hrtimer_restart result = HRTIMER_NORESTART; + bool must_restart_timer = false; + + // Start bit. + if (rx_bit_index == -1) + { + rx_bit_index++; + character = 0; + must_restart_timer = true; + } + + // Data bits. + else if (0 <= rx_bit_index && rx_bit_index < 8) + { + if (bit_value == 0) + { + character &= 0xfeff; + } + else + { + character |= 0x0100; + } + + rx_bit_index++; + character >>= 1; + must_restart_timer = true; + } + + // Stop bit. + else if (rx_bit_index == 8) + { + receive_character(character); + rx_bit_index = -1; + } + + // Restarts the RX timer. + if (must_restart_timer) + { + hrtimer_forward(&timer_rx, current_time, period); + result = HRTIMER_RESTART; + } + + return result; +} + +static int __init init(void) +{ + bool success = true; + + proc_create("gl_mifi_mcu", 0, NULL, &hello_proc_fops); + + success &= gpio_request(gpio_tx, "soft_uart_tx") == 0; + success &= gpio_direction_output(gpio_tx, 1) == 0; + success &= gpio_request(gpio_rx, "soft_uart_rx") == 0; + success &= gpio_direction_input(gpio_rx) == 0; + success &= gpio_set_debounce(gpio_rx, 1000/baudrate/2); + + success &= request_irq( + gpio_to_irq(gpio_rx), + (irq_handler_t) handle_rx_start, + IRQF_TRIGGER_FALLING, + "gl_mifi_mcu_irq_handler", + NULL) == 0; + + hrtimer_init(&timer_tx, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + timer_tx.function = &handle_tx; + hrtimer_init(&timer_rx, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + timer_rx.function = &handle_rx; + period = ktime_set(0, 1000000000/baudrate); + hrtimer_start(&timer_tx, period, HRTIMER_MODE_REL); + + return success; +} + +static void __exit exit(void) +{ + disable_irq(gpio_to_irq(gpio_rx)); + hrtimer_cancel(&timer_tx); + hrtimer_cancel(&timer_rx); + free_irq(gpio_to_irq(gpio_rx), NULL); + gpio_set_value(gpio_tx, 0); + gpio_free(gpio_tx); + gpio_free(gpio_rx); + remove_proc_entry("gl_mifi_mcu", NULL); +} + +module_init(init); +module_exit(exit); +