/* * MTS-IO Controller * * Copyright (C) 2014 by Multi-Tech Systems * Copyright (C) 2016 by Multi-Tech Systems * * Authors: James Maki * Jesse Gilles * Mike Fiore * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mts_io.h" #define DRIVER_VERSION "v1.5.7" #define DRIVER_AUTHOR "James Maki " #define DRIVER_DESC "MTS-IO Controller" #define DRIVER_NAME "mts-io" #define PLATFORM_NAME "mts-io" #define LED_LS_CONTROLLABLE 0 /* on-board EEPROM */ extern uint8_t mts_id_eeprom[512]; static struct mts_id_eeprom_layout id_eeprom; // NUM_AP should be defined from the board code // it should be set to the value of CONFIG_MTS_NUM_ACCESSORY_PORTS // arch/arm/mach-at91/board-dt-sam9.c // if it is 0 or undefined, there is no accessory card support on this HW #ifdef CONFIG_MTS_NUM_ACCESSORY_PORTS #ifndef NUM_AP #define NUM_AP CONFIG_MTS_NUM_ACCESSORY_PORTS #endif #else #define NUM_AP 0 #endif static uint8_t mts_hw_version; static struct platform_device *mts_io_platform_device; static struct attribute_group *attr_group; static struct attribute_group *attr_group_lora; // on-board lora peripheral to be stored in the lora/ sub-directory static struct gpio_pin *gpio_pins; static DEFINE_MUTEX(mts_io_mutex); static unsigned int *timings_data = NULL; static unsigned int timings_data_size = 0; static unsigned int timings_data_index = 0; static time_t timings_data_stop_seconds = 0; static struct timer_list radio_reset_timer; static volatile int radio_reset_timer_is_start = 0; static struct timer_list radio_reset_available_timer; static volatile int radio_reset_available_timer_is_start = 0; static time_t time_now_secs(void); static void radio_reset_available_timer_callback(unsigned long data); static void radio_reset_timer_callback(unsigned long data); /* generic GPIO support */ #include "gpio.c" /* reset button handling */ #define RESET_CHECK_PER_SEC 8 #define RESET_INTERVAL (HZ / RESET_CHECK_PER_SEC) #define RESET_HOLD_COUNT (RESET_CHECK_PER_SEC * 3) #define RESET_LONG_HOLD_COUNT (RESET_CHECK_PER_SEC * 30) static pid_t reset_pid = -1; static pid_t reset_count = 0; bool sent_extra_long = false; static int reset_short_signal = SIGUSR1; static int reset_long_signal = SIGUSR2; static int reset_extra_long_signal = SIGHUP; static int reset_short_interval = RESET_HOLD_COUNT; static int reset_long_interval = RESET_LONG_HOLD_COUNT; static void reset_callback(struct work_struct *ignored); static DECLARE_DELAYED_WORK(reset_work, reset_callback); static void reset_callback(struct work_struct *ignored) { struct gpio_pin *pin; int reset_pressed = 0; struct pid *vpid = NULL; mutex_lock(&mts_io_mutex); pin = gpio_pin_by_name("DEVICE_RESET"); if (pin) { reset_pressed = !gpio_get_value(pin->pin.gpio); } if (reset_pid > 0) { vpid = find_vpid(reset_pid); } if (vpid) { if (reset_pressed) { reset_count++; } else { //Reset button has not been pressed if (reset_count > 0 && reset_count < reset_short_interval) { kill_pid(vpid, reset_short_signal, 1); } else if (reset_count >= reset_short_interval && reset_count < reset_long_interval) { kill_pid(vpid, reset_long_signal, 1); } reset_count = 0; sent_extra_long = false; } if (reset_count >= reset_long_interval && ! sent_extra_long) { kill_pid(vpid, reset_extra_long_signal, 1); sent_extra_long = true; } } else { reset_count = 0; } mutex_unlock(&mts_io_mutex); schedule_delayed_work(&reset_work, RESET_INTERVAL); } static ssize_t mts_attr_show_reset_monitor_intervals(struct device *dev, struct device_attribute *attr, char *buf) { int ret; mutex_lock(&mts_io_mutex); ret = sprintf(buf, "%d %d\n", reset_short_interval / RESET_CHECK_PER_SEC, reset_long_interval / RESET_CHECK_PER_SEC); mutex_unlock(&mts_io_mutex); return ret; } static ssize_t mts_attr_store_reset_monitor_intervals(struct device *dev, struct device_attribute *attr, char *buf, size_t count) { int short_int; int long_int; if (sscanf(buf, "%i %i", &short_int, &long_int) != 2) { return -EINVAL; } mutex_lock(&mts_io_mutex); reset_short_interval = short_int * RESET_CHECK_PER_SEC; reset_long_interval = long_int * RESET_CHECK_PER_SEC; mutex_unlock(&mts_io_mutex); return count; } static DEVICE_ATTR_MTS(dev_attr_reset_monitor_intervals, "reset-monitor-intervals", mts_attr_show_reset_monitor_intervals, mts_attr_store_reset_monitor_intervals); static ssize_t mts_attr_show_reset_monitor(struct device *dev, struct device_attribute *attr, char *buf) { int ret; mutex_lock(&mts_io_mutex); ret = sprintf(buf, "%d %d %d %d\n", reset_pid, reset_short_signal, reset_long_signal, reset_extra_long_signal); mutex_unlock(&mts_io_mutex); return ret; } static ssize_t mts_attr_store_reset_monitor(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { pid_t pid; int short_signal; int long_signal; int extra_long_signal; int result = sscanf(buf, "%i %i %i %i", &pid, &short_signal, &long_signal, &extra_long_signal); if (result < 3 || result > 4) { return -EINVAL; } if(result == 3) { mutex_lock(&mts_io_mutex); reset_pid = pid; reset_short_signal = short_signal; reset_long_signal = long_signal; mutex_unlock(&mts_io_mutex); } else { mutex_lock(&mts_io_mutex); reset_pid = pid; reset_short_signal = short_signal; reset_long_signal = long_signal; reset_extra_long_signal = extra_long_signal; mutex_unlock(&mts_io_mutex); } return count; } static DEVICE_ATTR_MTS(dev_attr_reset_monitor, "reset-monitor", mts_attr_show_reset_monitor, mts_attr_store_reset_monitor); static DEVICE_ATTR_RO_MTS(dev_attr_reset, "reset", mts_attr_show_gpio_pin); /* active-low socket modem reset */ static ssize_t mts_attr_store_radio_reset(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int value; int err; struct gpio_pin *pin; if (sscanf(buf, "%i", &value) != 1) { return -EINVAL; } if (value != 0) { return -EINVAL; } /* check reset timings is enabled */ if (NULL != timings_data) { /* check reset timer is started */ if (radio_reset_timer_is_start == 1) { log_info("radio reset timer is running. \n"); return count; } /* check reset timer available is started */ if (radio_reset_available_timer_is_start == 1) { del_timer(&radio_reset_available_timer); radio_reset_available_timer_is_start = 0; } /* reset timer not started, start it */ mod_timer(&radio_reset_timer, jiffies + msecs_to_jiffies((timings_data[timings_data_index]) * 1000)); //log_info("radio reset timer is start = [%d]\n", time_now_secs()); /* save timings_data_stop_seconds */ timings_data_stop_seconds = timings_data[timings_data_index] + time_now_secs(); radio_reset_timer_is_start = 1; } log_info("radio is reset\n"); pin = gpio_pin_by_name("RADIO_RESET"); if (!pin) { return -ENODEV; } mutex_lock(&mts_io_mutex); // 250ms low reset err = reset_gpio_pin(pin, 250, 0); mutex_unlock(&mts_io_mutex); if (err) { return err; } return count; } static DEVICE_ATTR_MTS(dev_attr_radio_reset, "radio-reset", mts_attr_show_gpio_pin, mts_attr_store_radio_reset); /* shared gpio attributes */ static DEVICE_ATTR_MTS(dev_attr_radio_power, "radio-power", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); /* backoff-timers */ static time_t time_now_secs(void) { struct timespec ts = current_kernel_time(); return ts.tv_sec; } static void radio_reset_available_timer_callback( unsigned long data ) { /* do your timer stuff here */ //log_info("radio_reset_available_timer_callback\n"); //log_info("radio reset available timer is stop = [%d]\n", time_now_secs()); /* zero timings_data_index */ timings_data_index = 0; //log_info("timings data index is zero = [%d]\n", timings_data_index); radio_reset_available_timer_is_start = 0; } static void radio_reset_timer_callback( unsigned long data ) { /* do your timer stuff here */ //log_info("radio_reset_timer_callback\n"); //log_info("radio reset timer is stop = [%d]\n", time_now_secs()); /* increment timings_data_index */ timings_data_index++; if(timings_data_index >= timings_data_size) { timings_data_index = timings_data_size-1; } //log_info("timings data index = [%d]\n", timings_data_index); /* reset available timer not started, start it */ mod_timer(&radio_reset_available_timer, jiffies + msecs_to_jiffies((timings_data[timings_data_index]) * 1000)); //log_info("radio reset available timer is start = [%d]\n", time_now_secs()); radio_reset_available_timer_is_start = 1; radio_reset_timer_is_start = 0; } static ssize_t mts_attr_store_radio_reset_backoffs(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { char *timings_data_str = NULL; const char delimiter[] = " "; char * pch = NULL; unsigned int size = 0; /* free previous timings_data */ if (NULL != timings_data) { /* stop timers */ del_timer(&radio_reset_timer); del_timer(&radio_reset_available_timer); timings_data_index = 0; radio_reset_timer_is_start = 0; radio_reset_available_timer_is_start = 0; //log_info("free previous timings_data\n"); kfree(timings_data); timings_data = NULL; timings_data_size = 0; } /* make a copy */ if( NULL == (timings_data_str = kzalloc((strlen(buf) + 1), GFP_KERNEL)) ){ log_error("can`t allocate memory\n"); return -EINVAL; } //log_info("radio_reset_backoffs buf: [%s]", buf); strncpy(timings_data_str, buf, (strlen(buf) + 1)); /* get number of tokens */ while (NULL != (pch = strsep (&timings_data_str, delimiter))) { int value = 0; sscanf(pch, "%d", &value); //log_info("radio reset backoffs pch = [%s]\n", pch); if (value > 0){ size++; if (NULL == timings_data) { /* make alloc */ if (NULL == (timings_data = kmalloc(sizeof(unsigned int), GFP_KERNEL))) { log_error("radio reset backoffs can`t allocate memory\n"); goto free; } } else { /* make realloc */ if (NULL == (timings_data = krealloc(timings_data, size * sizeof(unsigned int), GFP_KERNEL))) { log_error("radio reset backoffs can`t allocate memory\n"); goto free; } } /* save timings data */ sscanf(pch, "%d", &timings_data[size-1]); } } timings_data_size = size; //log_info("timings_data_size = %d\n", timings_data_size); if (NULL != timings_data_str) { /* free timings_data_str */ /* never get here in happy path */ kfree(timings_data_str); } return count; free: if (NULL != timings_data_str) { /* free timings_data_str */ kfree(timings_data_str); } if (NULL != timings_data) { kfree(timings_data); timings_data = NULL; timings_data_size = 0; } return -EINVAL; } static ssize_t mts_attr_store_radio_reset_backoffs_index(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int value; if (sscanf(buf, "%d", &value) != 1) { return -EINVAL; } if ((value < 0) || (value >= timings_data_size)) { log_error("incorrect data\n"); return -EINVAL; } /* stop timers */ del_timer(&radio_reset_timer); del_timer(&radio_reset_available_timer); radio_reset_timer_is_start = 0; radio_reset_available_timer_is_start = 0; timings_data_index = value; return count; } static ssize_t mts_attr_show_radio_reset_backoffs(struct device *dev, struct device_attribute *attr, char *buf) { int ret = 0; size_t i = 0; size_t buf_left = 0; if (NULL != timings_data) { for(i = 0; i < timings_data_size; ++i) { buf_left = PAGE_SIZE - ret; ret += snprintf(buf += strlen(buf), buf_left, "%d ", timings_data[i]); } } if (ret > 0) { ret -= 1; } return ret; } static ssize_t mts_attr_show_radio_reset_backoff_index(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t value; if (strcmp(attr->attr.name, "radio-reset-backoff-index") == 0) { value = sprintf(buf, "%d", timings_data_index); } else { log_error("attribute '%s' not found", attr->attr.name); value = -1; } return value; } static ssize_t mts_attr_show_radio_reset_backoff_seconds(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t value; if (strcmp(attr->attr.name, "radio-reset-backoff-seconds") == 0) { if (radio_reset_timer_is_start == 1) { value = sprintf(buf, "%lu", (timings_data_stop_seconds - time_now_secs())); } else { value = sprintf(buf, "%d", 0); } } else { log_error("attribute '%s' not found", attr->attr.name); value = -1; } return value; } static DEVICE_ATTR_MTS(dev_attr_radio_reset_backoffs, "radio-reset-backoffs", mts_attr_show_radio_reset_backoffs, mts_attr_store_radio_reset_backoffs); static DEVICE_ATTR_MTS(dev_attr_radio_reset_backoff_index, "radio-reset-backoff-index", mts_attr_show_radio_reset_backoff_index, mts_attr_store_radio_reset_backoffs_index); static DEVICE_ATTR_RO_MTS(dev_attr_radio_reset_backoff_seconds, "radio-reset-backoff-seconds", mts_attr_show_radio_reset_backoff_seconds); /* shared gpio-based LEDs */ static DEVICE_ATTR_MTS(dev_attr_led_status, "led-status", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_led_a_gpio, "led-a", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); #if LED_LS_CONTROLLABLE static DEVICE_ATTR_MTS(dev_attr_led_ls, "led-ls", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); #else static DEVICE_ATTR_RO_MTS(dev_attr_led_ls, "led-ls", mts_attr_show_gpio_pin); #endif static DEVICE_ATTR_MTS(dev_attr_led_b_gpio, "led-b", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_led_cd_gpio, "led-cd", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_led_c_gpio, "led-c", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_led_sig1_gpio, "led-sig1", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_led_sig2_gpio, "led-sig2", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_led_sig3_gpio, "led-sig3", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_led_d_gpio, "led-d", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_led_e_gpio, "led-e", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); /* eeprom info */ static ssize_t mts_attr_show_product_info(struct device *dev, struct device_attribute *attr, char *buf) { int i; ssize_t value; if (strcmp(attr->attr.name, "vendor-id") == 0) { value = sprintf(buf, "%.32s\n", id_eeprom.vendor_id); } else if (strcmp(attr->attr.name, "product-id") == 0) { value = sprintf(buf, "%.32s\n", id_eeprom.product_id); } else if (strcmp(attr->attr.name, "device-id") == 0) { value = sprintf(buf, "%.32s\n", id_eeprom.device_id); } else if (strcmp(attr->attr.name, "uuid") == 0) { //Loop Through UUID Bytes and print them in HEX for(i = 0; i < 16; i++) { value = sprintf(buf, "%02X", id_eeprom.uuid[i]); if(value == -1) { return value; } buf += value; } value = sprintf(buf, "\n"); if(value == -1) { return value; } value = 33; //16*2 (ASCII HEX) + 1 ('\n') } else if (strcmp(attr->attr.name, "hw-version") == 0) { value = sprintf(buf, "%.32s\n", id_eeprom.hw_version); } else if (strcmp(attr->attr.name, "imei") == 0) { value = sprintf(buf, "%.32s\n", id_eeprom.imei); } else if (strcmp(attr->attr.name, "mac-eth") == 0) { value = sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X\n", id_eeprom.mac_addr[0], id_eeprom.mac_addr[1], id_eeprom.mac_addr[2], id_eeprom.mac_addr[3], id_eeprom.mac_addr[4], id_eeprom.mac_addr[5]); } else if (strcmp(attr->attr.name, "lora-eui") == 0) { value = sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n", id_eeprom.lora_eui[0], id_eeprom.lora_eui[1], id_eeprom.lora_eui[2], id_eeprom.lora_eui[3], id_eeprom.lora_eui[4], id_eeprom.lora_eui[5], id_eeprom.lora_eui[6], id_eeprom.lora_eui[7]); } else { log_error("attribute '%s' not found", attr->attr.name); value = -1; } return value; } static DEVICE_ATTR_RO_MTS(dev_attr_vendor_id, "vendor-id", mts_attr_show_product_info); static DEVICE_ATTR_RO_MTS(dev_attr_product_id, "product-id", mts_attr_show_product_info); static DEVICE_ATTR_RO_MTS(dev_attr_device_id, "device-id", mts_attr_show_product_info); static DEVICE_ATTR_RO_MTS(dev_attr_uuid, "uuid", mts_attr_show_product_info); static DEVICE_ATTR_RO_MTS(dev_attr_hw_version, "hw-version", mts_attr_show_product_info); static DEVICE_ATTR_RO_MTS(dev_attr_imei, "imei", mts_attr_show_product_info); static DEVICE_ATTR_RO_MTS(dev_attr_eth_mac, "mac-eth", mts_attr_show_product_info); /* include on-board lora peripheral */ #include "mts_lora.c" /* include per-device pins and attributes */ #include "mtcdt.c" #include "mtcap.c" /* include capabilities sub-directory support */ #include "mts_capab.c" #if NUM_AP > 0 /* accessory card EEPROMs */ extern uint8_t mts_ap_eeprom[NUM_AP][512]; static struct mts_ap_eeprom_layout ap_eeprom[NUM_AP]; /* kobject pointers for the apX subdirectories that correspond to the accessory ports */ static struct kobject *ap_subdirs[NUM_AP]; /* attribute groups for the accessory ports*/ static struct attribute_group ap_attr_groups[NUM_AP]; /* info for accessory port (contains function pointers for setup and teardown and and useful info) */ static struct ap_info* port_info[NUM_AP]; /* accessory card support */ #include "mtac.c" #include "mtac_gpiob.c" #include "mtac_mfser.c" #include "mtac_eth.c" #include "mtac_lora.c" static bool load_port(int port) { int port_index = port - 1; memcpy(&ap_eeprom[port_index], mts_ap_eeprom[port_index], sizeof(mts_ap_eeprom[port_index])); if (mts_ap_eeprom[port_index][0] == 0xFF) { log_error("uninitialized eeprom on accessory card %d", port); } else if (mts_ap_eeprom[port_index][0] == 0x00) { log_info("no accessory card inserted in port %d", port); } else { port_info[port_index] = kzalloc(sizeof(struct ap_info), GFP_KERNEL); if (! port_info[port_index]) { log_error("alloc of port info failed"); return false; } if (strstr(ap_eeprom[port_index].product_id, PRODUCT_ID_MTAC_GPIOB)) { if (! set_gpiob_info(port_info[port_index])) { log_error("failed to set up gpiob port info"); return false; } } else if (strstr(ap_eeprom[port_index].product_id, PRODUCT_ID_MTAC_MFSER)) { if (! set_mfser_info(port_info[port_index])) { log_error("failed to set up mfser port info"); return false; } } else if (strstr(ap_eeprom[port_index].product_id, PRODUCT_ID_MTAC_ETH)) { if (! set_eth_info(port_info[port_index])) { log_error("failed to set up eth port info"); return false; } } else if (strstr(ap_eeprom[port_index].product_id, PRODUCT_ID_MTAC_LORA)) { if (! set_lora_info(port_info[port_index])) { log_error("failed to set up lora port info"); return false; } } else { log_error("unknown accessory card [%s] in port %d", ap_eeprom[port_index].product_id, port); kfree(port_info[port_index]); port_info[port_index] = NULL; return false; } log_info("accessory card %d vendor-id: %.32s", port, ap_eeprom[port_index].vendor_id); log_info("accessory card %d product-id: %.32s", port, ap_eeprom[port_index].product_id); log_info("accessory card %d device-id: %.32s", port, ap_eeprom[port_index].device_id); log_info("accessory card %d hw-version: %.32s", port, ap_eeprom[port_index].hw_version); if (strncmp(ap_eeprom[port_index].product_id, PRODUCT_ID_MTAC_ETH, strlen(PRODUCT_ID_MTAC_ETH)) == 0) { log_info("accessory card %d mac-addr: %02X:%02X:%02X:%02X:%02X:%02X", port, ap_eeprom[port_index].mac_addr[0], ap_eeprom[port_index].mac_addr[1], ap_eeprom[port_index].mac_addr[2], ap_eeprom[port_index].mac_addr[3], ap_eeprom[port_index].mac_addr[4], ap_eeprom[port_index].mac_addr[5]); } if (strncmp(ap_eeprom[port_index].product_id, PRODUCT_ID_MTAC_LORA, strlen(PRODUCT_ID_MTAC_LORA)) == 0) { log_info("accessory card %d eui: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", port, ap_eeprom[port_index].eui[0], ap_eeprom[port_index].eui[1], ap_eeprom[port_index].eui[2], ap_eeprom[port_index].eui[3], ap_eeprom[port_index].eui[4], ap_eeprom[port_index].eui[5], ap_eeprom[port_index].eui[6], ap_eeprom[port_index].eui[7]); } if (! port_info[port_index]->setup(port)) { log_error("accessory port %d setup failed", port); port_info[port_index]->teardown(port); kfree(port_info[port_index]); port_info[port_index] = NULL; return false; } } return true; } static void init_accessory_ports(void) { int port_index; for (port_index = 0; port_index < NUM_AP; port_index++) { port_info[port_index] = NULL; if (! load_port(port_index+1)) { log_error("failed to load accessory card in port %d", port_index); } } } static void teardown_accessory_ports(void) { int port_index; for (port_index = 0; port_index < NUM_AP; port_index++) { if (port_info[port_index]) { port_info[port_index]->teardown(port_index+1); kfree(port_info[port_index]); } } } #else /* NUM_AP > 0 */ static void init_accessory_ports(void) {} static void teardown_accessory_ports(void) {} #endif static void init_ports(void) { if (DEVICE_CAPA(id_eeprom.capa, CAPA_LORA) && attr_group_lora) { mts_load_lora_port(); } init_accessory_ports(); } static void teardown_ports(void) { if (DEVICE_CAPA(id_eeprom.capa, CAPA_LORA) && attr_group_lora) { mts_teardown_lora_port(); } teardown_accessory_ports(); } struct attribute *freelater = NULL; // Storage to free when driver is unloaded. static int mts_id_eeprom_load() { int i; char buf[64] = {0}; char* ptr; int attr_blength; // Byte length of base attribute array int current_blength; // Current length in bytes of attribute array int current_count; // Number of items in array struct attribute **all_attrs = NULL; mts_hw_version = MTCDT_0_0; //The mts_id_eeprom buffer is initialize once on boot //reloading the mts_io.ko module will not reinitialize this buffer //only rebooting will reinitialize this buffer memcpy(&id_eeprom, mts_id_eeprom, sizeof(mts_id_eeprom)); if (mts_id_eeprom[0] == 0xFF) { log_error("uninitialized eeprom"); return -EIO; } else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTCAP_0_0, strlen(HW_VERSION_MTCAP_0_0)) == 0) { attr_group = &mtcap_0_0_platform_attribute_group; gpio_pins = gpio_pins_mtcap_0_0; if (DEVICE_CAPA(id_eeprom.capa, CAPA_LORA)) { attr_group_lora = &mtcap_0_0_lora_attribute_group; } mts_hw_version = MTCAP_0_0; log_info("detected board %s", HW_VERSION_MTCAP_0_0); } else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTCDT_0_1, strlen(HW_VERSION_MTCDT_0_1)) == 0) { attr_blength = sizeof mtcdt_0_1_platform_attributes; if(DEVICE_CAPA(id_eeprom.capa, CAPA_WIFI)) { attr_blength += sizeof mtcdt_0_1_wifi_bt_attributes; } if(DEVICE_CAPA(id_eeprom.capa, CAPA_GPS)) { attr_blength += sizeof mtcdt_0_1_gnss_attributes; } if (attr_blength != sizeof mtcdt_0_1_platform_attributes) { freelater = all_attrs = kmalloc(attr_blength,GFP_KERNEL); current_blength = sizeof mtcdt_0_1_platform_attributes - sizeof (struct attribute *); current_count = current_blength/(sizeof (struct attribute *)); memcpy(all_attrs,mtcdt_0_1_platform_attributes,current_blength); if(DEVICE_CAPA(id_eeprom.capa, CAPA_WIFI)) { memcpy(all_attrs + current_count,mtcdt_0_1_wifi_bt_attributes,sizeof mtcdt_0_1_wifi_bt_attributes); current_count += sizeof mtcdt_0_1_wifi_bt_attributes / (sizeof (struct attribute *)); } if(DEVICE_CAPA(id_eeprom.capa, CAPA_GPS)) { attr_blength += sizeof mtcdt_0_1_gnss_attributes; memcpy(all_attrs + current_count,mtcdt_0_1_gnss_attributes,sizeof mtcdt_0_1_gnss_attributes); current_count += sizeof mtcdt_0_1_gnss_attributes / (sizeof (struct attribute *)); } all_attrs[current_count] = (struct attribute *)NULL; mtcdt_0_1_platform_attribute_group.attrs = all_attrs; } attr_group = &mtcdt_0_1_platform_attribute_group; gpio_pins = gpio_pins_mtcdt_0_1; mts_hw_version = MTCDT_0_1; log_info("detected board %s", HW_VERSION_MTCDT_0_1); } else { attr_group = &mtcdt_platform_attribute_group; gpio_pins = gpio_pins_mtcdt_0_0; mts_hw_version = MTCDT_0_0; log_info("detected board %s", HW_VERSION_MTCDT_0_0); } log_info("sizeof: %lu", (unsigned long) sizeof(struct mts_id_eeprom_layout)); log_info("vendor-id: %.32s", id_eeprom.vendor_id); log_info("product-id: %.32s", id_eeprom.product_id); log_info("device-id: %.32s", id_eeprom.device_id); log_info("hw-version: %.32s", id_eeprom.hw_version); log_info("mac-addr: %02X:%02X:%02X:%02X:%02X:%02X", id_eeprom.mac_addr[0], id_eeprom.mac_addr[1], id_eeprom.mac_addr[2], id_eeprom.mac_addr[3], id_eeprom.mac_addr[4], id_eeprom.mac_addr[5]); log_info("imei: %.32s", id_eeprom.imei); log_info("capa-gps: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_GPS) ? "yes" : "no"); log_info("capa-din: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_DIN) ? "yes" : "no"); log_info("capa-dout: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_DOUT) ? "yes" : "no"); log_info("capa-adc: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_ADC) ? "yes" : "no"); log_info("capa-wifi: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_WIFI) ? "yes" : "no"); log_info("capa-bluetooth: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_BLUETOOTH) ? "yes" : "no"); log_info("capa-lora: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_LORA) ? "yes" : "no"); if (DEVICE_CAPA(id_eeprom.capa, CAPA_BLUETOOTH)) { log_info("mac-bluetooth: %02X:%02X:%02X:%02X:%02X:%02X", id_eeprom.mac_bluetooth[0], id_eeprom.mac_bluetooth[1], id_eeprom.mac_bluetooth[2], id_eeprom.mac_bluetooth[3], id_eeprom.mac_bluetooth[4], id_eeprom.mac_bluetooth[5]); } if (DEVICE_CAPA(id_eeprom.capa, CAPA_WIFI)) { log_info("mac-wifi: %02X:%02X:%02X:%02X:%02X:%02X", id_eeprom.mac_wifi[0], id_eeprom.mac_wifi[1], id_eeprom.mac_wifi[2], id_eeprom.mac_wifi[3], id_eeprom.mac_wifi[4], id_eeprom.mac_wifi[5]); } //Loop Through UUID Bytes and print them in HEX ptr = (char*)buf; for(i = 0; i < 16; i++) { ptr += sprintf(ptr, "%02X", id_eeprom.uuid[i]); } log_info("uuid: %s", (char*)buf); if (DEVICE_CAPA(id_eeprom.capa, CAPA_LORA)) { log_info("lora-eui: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", id_eeprom.lora_eui[0], id_eeprom.lora_eui[1], id_eeprom.lora_eui[2], id_eeprom.lora_eui[3], id_eeprom.lora_eui[4], id_eeprom.lora_eui[5], id_eeprom.lora_eui[6], id_eeprom.lora_eui[7]); log_info("lora-product-id: %.32s", id_eeprom.lora_product_id); log_info("lora-hw-version: %.32s", id_eeprom.lora_hw_version); } return 0; } static void cleanup(void) { log_info("cleaning up...."); if (mts_io_platform_device) { platform_device_unregister(mts_io_platform_device); } teardown_ports(); if(freelater) { kfree(freelater); freelater = NULL; } mts_capab_dir_delete(); } static int __init mts_io_init(void) { struct gpio_pin *pin; int ret; log_info("init: " DRIVER_VERSION); ret = mts_id_eeprom_load(); if (ret) { cleanup(); return ret; } mts_io_platform_device = platform_device_alloc(PLATFORM_NAME, -1); if (!mts_io_platform_device) { cleanup(); return -ENOMEM; } ret = platform_device_add(mts_io_platform_device); if (ret) { cleanup(); return ret; } init_ports(); ret = mts_capab_dir_create(); if (ret) { cleanup(); return ret; } ret = sysfs_create_group(&mts_io_platform_device->dev.kobj, attr_group); if (ret) { cleanup(); return ret; } for (pin = gpio_pins; *pin->name; pin++) { if (pin->capability == 0 || DEVICE_CAPA(id_eeprom.capa,pin->capability)) { ret = gpio_request_one(pin->pin.gpio, pin->pin.flags, pin->pin.label); if (ret) log_debug("could not request pin %s (%d) but it could have already been requested under a different pin name", pin->name, ret); } } // start the reset handler reset_callback(NULL); /* init timers */ setup_timer(&radio_reset_timer, radio_reset_timer_callback, 0); setup_timer(&radio_reset_available_timer, radio_reset_available_timer_callback, 0); return 0; } static void __exit mts_io_exit(void) { /* delete radio_reset_timer */ del_timer(&radio_reset_timer); /* delete radio_reset_available_timer */ del_timer(&radio_reset_available_timer); cancel_delayed_work_sync(&reset_work); cleanup(); log_info("exiting"); } module_init(mts_io_init); module_exit(mts_io_exit); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_VERSION(DRIVER_VERSION); MODULE_LICENSE("GPL"); MODULE_ALIAS("mts-io-ap1-dout"); MODULE_ALIAS("mts-io-ap1-din"); MODULE_ALIAS("mts-io-ap1-adc"); MODULE_ALIAS("mts-io-ap2-dout"); MODULE_ALIAS("mts-io-ap2-din"); MODULE_ALIAS("mts-io-ap2-adc");