/* * MTS-IO Controller * * Copyright (C) 2014 by Multi-Tech Systems * Copyright (C) 2016 by Multi-Tech Systems * Copyright (C) 2019 by Multi-Tech Systems * Copyright (C) 2020 by Multi-Tech Systems * * 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 #include #include "at91gpio.h" #include "mts_io_module.h" #include "version.h" #include "mts_io.h" #include "buttons.h" #include "mts_supercap.h" #include "radio_udev_discovery.h" #define PLATFORM_NAME "mts-io" #define LED_LS_CONTROLLABLE 0 /* To use AT91 pinctrl to set pull-up * and pull-down, we use device tree. * This seems the easiest way. Most * drivers implement pinctrl in the * probe function, which causes * device tree to be read, and ultimately * causes the at91 pinctrl code to * set the pull-up/pulldown registers * as specified in the mts-io * pinctrl section of device tree. * .compatible lets us find our device * tree entries. Probe and remove * are mandatory even though our code * is a no-op. This probe code was based * on the i2c-gpio driver. */ static const struct of_device_id mts_io_dt_ids[] = { { .compatible = "mts,mts-io", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, mts_io_dt_ids); /* on-board EEPROM */ static struct mts_id_eeprom_layout id_eeprom; // Allow other modules to query the hardware version const char *mts_get_hw_version(void) { return id_eeprom.hw_version; } EXPORT_SYMBOL(mts_get_hw_version); #include "adc.c" static int mts_io_probe(struct platform_device *pdev) { return 0; } static int mts_io_remove(struct platform_device *pdev) { return 0; } /* * minimal required functions for the platform driver. */ static struct platform_driver mts_io_driver = { .driver = { .name = "mts-io", .of_match_table = of_match_ptr(mts_io_dt_ids), }, .probe = mts_io_probe, .remove = mts_io_remove, }; static uint8_t mts_hw_version; struct platform_device *mts_io_platform_device; EXPORT_SYMBOL(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; 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 time64_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 time64_t time_now_secs(void); /* generic GPIO support */ #include "gpio.c" #include "spi.c" /* generic Button support */ //#include "buttons.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) bool sent_extra_long = false; /* * For EEPROM layout after version 0, we use the * CAPA_CELLULAR flag to determine if we have a cellular * modem and this code is not used. * * Version 0 uses the product-id to determine if there is * a modem or not. * * Saying there is a modem when there is not * just results in a slower boot. * If no hyphen in product ID, or no product ID, * assume we have a radio. * If there is a hyphen, test the character after the * first hyphen: * If the character is numeric, we have no modem. * if the chracter is not a B or an I, we have a modem. * If the character is a B, and it is followed * by a numeric, we have no modem. * If the B is the last character we have a modem. * If the modem field starts with IN, we have an * India LoRa channel plan with no cellular modem. * All other cases, we have a modem. * * TRUE: 1 for return result * FALSE: 0 for return result */ int mts_has_radio(const char *product_id, size_t len) { char *p; int found_i = 0; if (!product_id || ! *product_id) return 1; /* No Product ID? */ p = memchr(product_id,'-',len); if (p) { /* Found a hyphen */ log_debug("Found hyphen"); p++; if (p >= product_id+len) { log_debug("End of string -- hyphen"); return 1; /* Last character was hyphen */ } if (isdigit(*p)) { log_debug("Found digit after hypen"); return 0; /* Modem name never starts with a digit */ } /* For eeprom version 0, if the 2nd field starts with a B[n] * it is a build #, and does not have a modem. If the 2nd field * starts with an IN8, it is an Indian LoRa channel plan, * and does not have a modem. */ if (*p == 'I') found_i = 1; if ((*p != 'B') && (! found_i)) { log_debug("Did not find B, I, or digit after hyphen in product-id"); return 1; /* Modem starting with a letter, but not B or I */ } /* Found a B or an I */ p++; if (p >= product_id+len) { log_debug("B or I at end of product-id string"); return 1; /* Last character was B or I */ } /* No modem starts with IN */ if (found_i && (*p == 'N')) { log_debug("IN found in 2nd product-id field -- India LoRa Channel plan"); return 0; } if (isdigit(*p)) { log_debug("B followed by digit after hyphen - no modem"); return 0; /* B[numeric] is MTR Build number */ } log_debug("B followed by non-digit after hyphen - has modem"); return 1; /* B[non-numeric] so assume a modem that starts with B */ } /* End of found hyphen case */ log_debug("Undefined product-id - has modem"); return 1; /* Product id invalid or empty, so instantiate a radio anyway */ } EXPORT_SYMBOL(mts_has_radio); /* 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; /* 0 = normal reset; -1 = forced reset */ int err; struct gpio_pin *pin; if (sscanf(buf, "%i", &value) != 1) { return -EINVAL; } if (value != 0 && value != -1) { return -EINVAL; } /* check reset timings is enabled */ if (value != -1 && 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; } reset_radio_udev_discovery(); log_info("radio is reset\n"); pin = gpio_pin_by_attr_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 time64_t time_now_secs(void) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,20,0) return ktime_get_real_seconds(); #else struct timespec ts = current_kernel_time(); return ts.tv_sec; #endif } #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0) static void radio_reset_available_timer_callback( struct timer_list *data ) #else static void radio_reset_available_timer_callback( unsigned long data ) #endif { /* 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; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0) static void radio_reset_timer_callback( struct timer_list *data ) #else static void radio_reset_timer_callback( unsigned long data ) #endif { /* 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, "%lld", (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); static DEVICE_ATTR_MTS(dev_attr_radio_udev_discovery, "radio-udev-discovery", mts_attr_show_radio_udev_discovery, mts_attr_store_radio_udev_discovery); static DEVICE_ATTR_MTS(dev_attr_radio_reset_monitor, "radio-reset-monitor", mts_attr_show_radio_reset_monitor, mts_attr_store_radio_reset_monitor); /* 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); 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, "has-radio") == 0) { if(id_eeprom.eeprom_layout_version == 0) value = sprintf(buf, "%d\n", mts_has_radio(id_eeprom.product_id,sizeof id_eeprom.product_id)); else /* Newer EEPROM version with CAPA_CELLULAR */ value = sprintf(buf, "%1d\n",DEVICE_CAPA(id_eeprom.capa, CAPA_CELLULAR) ? 1 : 0); } 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-wifi") == 0) { value = sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X\n", 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]); } else if (strcmp(attr->attr.name, "mac-bluetooth") == 0) { value = sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X\n", 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]); } 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]); #ifdef MTRE } else if (strcmp(attr->attr.name, "oem-string1") == 0) { value = sprintf(buf, "%.32s\n", id_eeprom.oem_string1); } else if (strcmp(attr->attr.name, "oem-string2") == 0) { value = sprintf(buf, "%.32s\n", id_eeprom.oem_string2); #endif } 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_has_radio, "has-radio", 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); #ifdef MTRE static DEVICE_ATTR_RO_MTS(dev_attr_oem_string1, "oem-string1", mts_attr_show_product_info); static DEVICE_ATTR_RO_MTS(dev_attr_oem_string2, "oem-string2", mts_attr_show_product_info); #endif static int get_radio_model_from_product_id(void) { int rc = RADIO_UNKNOWN; if (strstr(id_eeprom.product_id, "LEU1")) rc = RADIO_LEU1; else if (strstr(id_eeprom.product_id, "LNA3")) rc = RADIO_LNA3; else if (strstr(id_eeprom.product_id, "LEU3")) rc = RADIO_LEU3; // Add other radios as needed. return rc; } /* include on-board lora peripheral */ #include "mts_lora.c" /* interface to Linux /sys/class/leds/ devices */ #include "mts_leds.c" /* include per-device pins and attributes */ #include "machine/mtcdt.c" #include "machine/mtcap.c" #include "machine/mtr.c" #include "machine/mtre.c" #include "machine/mths.c" #include "machine/mtcpm.c" #include "machine/mt100eocg.c" #include "machine/mtcap3.c" /* include capabilities sub-directory support */ #include "mts_capab.c" struct attribute **freelater = NULL; // Storage to free when driver is unloaded. static int mts_id_eeprom_load(void) { int i, j = 0; 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; char *tmp; int noradio = 0; // MTCPM-0.0 does not trim off radio-reset, etc. int has_cellular_capaflag = 0; int need_append; int ret; const struct firmware* fw = NULL; /* Attempt to load the mts-io driver */ if((ret = request_firmware_direct(&fw, "0-0056/eeprom", &mts_io_platform_device->dev)) == 0) { if(fw->size == sizeof(id_eeprom)) { memcpy(&id_eeprom, fw->data, sizeof(id_eeprom)); log_info("Platform EEPROM contents loaded"); } else { log_error("Invalid platform EEPROM length (%d)", fw->size); return -EINVAL; } release_firmware(fw); } else { log_error("Unable to load EEPROM contents (%d)", ret); return -ENODEV; } /* If we are an MTCPM-0.0, the base board sets the radio existance, and we always * add the radio-reset, etc */ if (strncmp(id_eeprom.hw_version,HW_VERSION_MTCPM_0_0,sizeof HW_VERSION_MTCPM_0_0) != 0) { if (id_eeprom.eeprom_layout_version == 0) { noradio = ! mts_has_radio(id_eeprom.product_id,sizeof id_eeprom.product_id); } else { has_cellular_capaflag = 1; noradio = ! DEVICE_CAPA(id_eeprom.capa, CAPA_CELLULAR); } log_debug("mts_id_eeprom_load: noradio=%d",noradio); } if (((tmp=HW_VERSION_MTCAP_0_0),(mts_hw_version=MTCAP_0_0),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0) || ((tmp=HW_VERSION_MTCAP_0_1),(mts_hw_version=MTCAP_0_1),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0) || ((tmp=HW_VERSION_MTCAP_0_2),(mts_hw_version=MTCAP_0_2),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0) || ((tmp=HW_VERSION_MTCAP_0_3),(mts_hw_version=MTCAP_0_3),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0)) { int need_radio_enable = 0; need_append = 0; current_blength = attr_blength = sizeof mtcap_0_0_platform_attributes; current_blength -= sizeof(struct attribute *); /* Length without terminating NULL */ /* See if we have no radio, and if so, prune out the stuff that follows */ if(noradio) { struct attribute **ap = mtcap_0_0_platform_attribute_group.attrs; while(1) { if(ap[j] == NULL) { log_info("Did not find radio power attribute. Possible driver fault."); break; } j++; if (is_radio_power_attr_mtcap(ap[j])) { log_info("Pruning radio feature from mts-io",j); ap[j] = NULL; current_blength = attr_blength = j * sizeof (ap[j]); /* account for removed attributes in table length */ break; } } } if(DEVICE_CAPA(id_eeprom.capa, CAPA_GPS)) { attr_blength += sizeof mtcap_0_3_gnss_attributes; need_append = 1; } if(DEVICE_CAPA(id_eeprom.capa, CAPA_SUPERCAP)) { attr_blength += sizeof mtcap_0_3_supercap_attributes; need_append = 1; } if(DEVICE_CAPA(id_eeprom.capa, CAPA_WIFI)) { attr_blength += sizeof mtcap_0_0_wifi_attributes; need_append = 1; } if(((tmp=HW_VERSION_MTCAP_0_2),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0) || ((tmp=HW_VERSION_MTCAP_0_3),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0)) { need_radio_enable = 1; need_append = 1; attr_blength += sizeof mtcap_0_2_enable_radio_attribute; } if (need_append) { freelater = all_attrs = kmalloc(attr_blength,GFP_KERNEL); current_count = current_blength/(sizeof (struct attribute *)); memcpy(all_attrs,mtcap_0_0_platform_attributes,current_blength); if(DEVICE_CAPA(id_eeprom.capa, CAPA_GPS)) { log_info("Adding GPS to mts-io driver"); memcpy(all_attrs + current_count,mtcap_0_3_gnss_attributes,sizeof mtcap_0_3_gnss_attributes); current_count += sizeof mtcap_0_3_gnss_attributes / (sizeof (struct attribute *)); } if(DEVICE_CAPA(id_eeprom.capa, CAPA_SUPERCAP)) { log_info("Adding supercap to mts-io driver"); memcpy(all_attrs + current_count,mtcap_0_3_supercap_attributes,sizeof mtcap_0_3_supercap_attributes); current_count += sizeof mtcap_0_3_supercap_attributes / (sizeof (struct attribute *)); } if(DEVICE_CAPA(id_eeprom.capa, CAPA_WIFI)) { log_info("Adding Wi-Fi to mts-io driver"); memcpy(all_attrs + current_count,mtcap_0_0_wifi_attributes,sizeof mtcap_0_0_wifi_attributes); current_count += sizeof mtcap_0_0_wifi_attributes / (sizeof (struct attribute *)); } if (need_radio_enable) { log_info("Adding Radio Enable to mts-io driver"); memcpy(all_attrs + current_count,mtcap_0_2_enable_radio_attribute,sizeof mtcap_0_2_enable_radio_attribute); current_count += sizeof mtcap_0_2_enable_radio_attribute / (sizeof (struct attribute *)); } all_attrs[current_count] = (struct attribute *)NULL; mtcap_0_0_platform_attribute_group.attrs = all_attrs; } attr_group = &mtcap_0_0_platform_attribute_group; gpio_pins = gpio_pins_mtcap_0_0; set_buttons(default_buttons); if (DEVICE_CAPA(id_eeprom.capa, CAPA_LORA)) { attr_group_lora = &mtcap_0_0_lora_attribute_group; } log_info("detected board %s", tmp); } else if ((tmp=HW_VERSION_MTCAP3_0_0),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0) { current_blength = attr_blength = sizeof mtcap3_0_0_platform_attributes; current_blength -= sizeof(struct attribute *); /* Length without terminating NULL */ if(noradio) { struct attribute **ap = mtcap3_0_0_platform_attribute_group.attrs; while(1) { if(ap[j] == NULL) { log_info("Did not find radio reset attribute. Possible driver fault."); break; } j++; if (is_radio_power_attr_mtcap3(ap[j])) { log_info("Pruning radio feature from mts-io",j); ap[j] = NULL; current_blength = j * sizeof (ap[j]); /* Size without NULL */ attr_blength += sizeof (ap[j]); /* Size of attr array with NULL */ break; } } } if (DEVICE_CAPA(id_eeprom.capa, CAPA_LORA)) { attr_group_lora = &mtcap3_0_0_lora_attribute_group; } attr_group = &mtcap3_0_0_platform_attribute_group; gpio_pins = gpio_pins_mtcap3_0_0; set_buttons(default_buttons); mts_hw_version = MTCAP3_0_0; log_info("detected board %s", HW_VERSION_MTCAP3_0_0); } else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTRE, strlen(HW_VERSION_MTRE)) == 0) { attr_group = &mtre_0_0_platform_attribute_group; gpio_pins = gpio_pins_mtre_0_0; set_buttons(default_buttons); mts_leds = mtre_0_0_leds; mts_hw_version = MTRE_0_0; log_info("detected board %s", HW_VERSION_MTRE); } else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTR_0_0, strlen(HW_VERSION_MTR_0_0)) == 0) { attr_group = &mtr_platform_attribute_group; gpio_pins = gpio_pins_mtr_0_0; set_buttons(default_buttons); mts_hw_version = MTR_0_0; log_info("detected board %s", HW_VERSION_MTR_0_0); } else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTR_0_1, strlen(HW_VERSION_MTR_0_1)) == 0) { attr_group = &mtr_platform_attribute_group; gpio_pins = gpio_pins_mtr_0_1; set_buttons(default_buttons); mts_hw_version = MTR_0_1; log_info("detected board %s", HW_VERSION_MTR_0_1); } else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTRV1_0_0, strlen(HW_VERSION_MTRV1_0_0)) == 0) { attr_group = &mtr_platform_attribute_group; gpio_pins = gpio_pins_mtrv1_0_0; set_buttons(default_buttons); mts_hw_version = MTRV1_0_0; log_info("detected board %s", HW_VERSION_MTRV1_0_0); } else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTRV1_0_1, strlen(HW_VERSION_MTRV1_0_1)) == 0) { attr_group = &mtrv1_0_1_platform_attribute_group; gpio_pins = gpio_pins_mtrv1_0_1; set_buttons(default_buttons); mts_hw_version = MTRV1_0_1; log_info("detected board %s", HW_VERSION_MTRV1_0_1); } else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTRV1_0_2, strlen(HW_VERSION_MTRV1_0_2)) == 0) { attr_group = &mtrv1_0_2_platform_attribute_group; gpio_pins = gpio_pins_mtrv1_0_2; set_buttons(default_buttons); mts_hw_version = MTRV1_0_2; log_info("detected board %s", HW_VERSION_MTRV1_0_2); } else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTRV1_0_3, strlen(HW_VERSION_MTRV1_0_3)) == 0) { attr_group = &mtrv1_0_3_platform_attribute_group; gpio_pins = gpio_pins_mtrv1_0_3; set_buttons(default_buttons); mts_hw_version = MTRV1_0_3; log_info("detected board %s", HW_VERSION_MTRV1_0_3); } else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTRV1_0_4, strlen(HW_VERSION_MTRV1_0_4)) == 0) { attr_group = &mtrv1_0_4_platform_attribute_group; gpio_pins = gpio_pins_mtrv1_0_4; set_buttons(default_buttons); mts_hw_version = MTRV1_0_4; mtr_setup_radio_status(id_eeprom.product_id); log_info("detected board %s", HW_VERSION_MTRV1_0_4); } else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTHS_0_0, strlen(HW_VERSION_MTHS_0_0)) == 0) { attr_group = &mths_0_0_platform_attribute_group; gpio_pins = gpio_pins_mths_0_0; set_buttons(mths_buttons_0_0); mts_hw_version = MTHS_0_0; log_info("detected board %s", HW_VERSION_MTHS_0_0); } else if (strncmp(id_eeprom.hw_version, HW_VERSION_MTHS_0_1, strlen(HW_VERSION_MTHS_0_1)) == 0) { attr_group = &mths_0_0_platform_attribute_group; gpio_pins = gpio_pins_mths_0_1; set_buttons(mths_buttons_0_0); /* Only one button version */ mts_hw_version = MTHS_0_1; log_info("detected board %s", HW_VERSION_MTHS_0_1); } else if (((tmp=HW_VERSION_MTCDT_0_1),(mts_hw_version=MTCDT_0_1),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0) || ((tmp=HW_VERSION_MTCDTIP_0_0),(mts_hw_version=MTCDTIP_0_0),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0)) { need_append = 0; current_blength = attr_blength = sizeof mtcdt_0_1_platform_attributes; current_blength -= sizeof(struct attribute *); /* Length without terminating NULL */ /* See if we have no radio, and if so, prune out the stuff that follows */ if(noradio) { struct attribute **ap = mtcdt_0_1_platform_attribute_group.attrs; while(1) { if(ap[j] == NULL) { log_info("Did not find radio power attribute. Possible driver fault."); break; } j++; if (is_radio_power_attr_mtcdt(ap[j])) { log_info("Pruning radio feature from mts-io",j); ap[j] = NULL; current_blength = j * sizeof (ap[j]); /* Size without NULL */ attr_blength += sizeof (ap[j]); /* Size of attr array with NULL */ break; } } } if(DEVICE_CAPA(id_eeprom.capa, CAPA_WIFI)) { attr_blength += sizeof mtcdt_0_1_wifi_bt_attributes; need_append = 1; } if(DEVICE_CAPA(id_eeprom.capa, CAPA_GPS)) { attr_blength += sizeof mtcdt_0_1_gnss_attributes; need_append = 1; } if (need_append) { freelater = all_attrs = kmalloc(attr_blength,GFP_KERNEL); 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)) { log_info("Adding WiFi/BT to mts-io driver"); 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)) { log_info("Adding GPS to mts-io driver"); 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; set_buttons(default_buttons); log_info("detected board %s", tmp); } else if (((tmp=HW_VERSION_MTCDT_0_2),(mts_hw_version=MTCDT_0_2),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0) || ((tmp=HW_VERSION_MTCDTIP_0_1),(mts_hw_version=MTCDTIP_0_1),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0)) { need_append = 0; current_blength = attr_blength = sizeof mtcdt_0_2_platform_attributes; current_blength -= sizeof(struct attribute *); /* Length without terminating NULL */ /* See if we have no radio, and if so, prune out the stuff that follows */ if(noradio) { struct attribute **ap = mtcdt_0_2_platform_attribute_group.attrs; while(1) { if(ap[j] == NULL) { log_info("Did not find radio power attribute. Possible driver fault."); break; } j++; if (is_radio_power_attr_mtcdt(ap[j])) { log_info("Pruning radio feature from mts-io",j); ap[j] = NULL; current_blength = j * sizeof (ap[j]); /* Size without NULL */ attr_blength += sizeof (ap[j]); /* Size of attr array with NULL */ break; } } } if(DEVICE_CAPA(id_eeprom.capa, CAPA_WIFI)) { attr_blength += sizeof mtcdt_0_2_wifi_bt_attributes; need_append = 1; } if(DEVICE_CAPA(id_eeprom.capa, CAPA_GPS)) { attr_blength += sizeof mtcdt_0_2_gnss_attributes; need_append = 1; } if (need_append) { freelater = all_attrs = kmalloc(attr_blength,GFP_KERNEL); current_count = current_blength/(sizeof (struct attribute *)); memcpy(all_attrs,mtcdt_0_2_platform_attributes,current_blength); if(DEVICE_CAPA(id_eeprom.capa, CAPA_WIFI)) { log_info("Adding WiFi/BT to mts-io driver"); memcpy(all_attrs + current_count,mtcdt_0_2_wifi_bt_attributes,sizeof mtcdt_0_2_wifi_bt_attributes); current_count += sizeof mtcdt_0_2_wifi_bt_attributes / (sizeof (struct attribute *)); } if(DEVICE_CAPA(id_eeprom.capa, CAPA_GPS)) { log_info("Adding GPS to mts-io driver"); attr_blength += sizeof mtcdt_0_2_gnss_attributes; memcpy(all_attrs + current_count,mtcdt_0_2_gnss_attributes,sizeof mtcdt_0_2_gnss_attributes); current_count += sizeof mtcdt_0_2_gnss_attributes / (sizeof (struct attribute *)); } all_attrs[current_count] = (struct attribute *)NULL; mtcdt_0_2_platform_attribute_group.attrs = all_attrs; } attr_group = &mtcdt_0_2_platform_attribute_group; gpio_pins = gpio_pins_mtcdt_0_2; set_buttons(default_buttons); log_info("detected board %s", tmp); } else if ((tmp=HW_VERSION_MTCDTIPHP_0_0),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0) { need_append = 0; current_blength = attr_blength = sizeof mtcdt_0_1_platform_attributes; current_blength -= sizeof(struct attribute *); /* Length without terminating NULL */ /* See if we have no radio, and if so, prune out the stuff that follows */ if(noradio) { struct attribute **ap = mtcdt_0_1_platform_attribute_group.attrs; while(1) { if(ap[j] == NULL) { log_info("Did not find radio power attribute. Possible driver fault."); break; } j++; if (is_radio_power_attr_mtcdt(ap[j])) { log_info("Pruning radio feature from mts-io",j); ap[j] = NULL; current_blength = j * sizeof (ap[j]); /* Size without NULL */ attr_blength += sizeof (ap[j]); /* Size of attr array with NULL */ break; } } } if(DEVICE_CAPA(id_eeprom.capa, CAPA_WIFI)) { attr_blength += sizeof mtcdt_0_1_wifi_bt_attributes; need_append = 1; } if (need_append) { freelater = all_attrs = kmalloc(attr_blength,GFP_KERNEL); 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)) { log_info("Adding WiFi/BT to mts-io driver"); 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 *)); } /* MTCDTIPHP does not have GPS reset/interrupt tied to the CPU * so do not instantiate the GPS attributes */ 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; if (DEVICE_CAPA(id_eeprom.capa, CAPA_LORA)) { attr_group_lora = &mtcdtiphp_0_0_lora_attribute_group; } set_buttons(default_buttons); mts_hw_version = MTCDTIPHP_0_0; log_info("detected board %s", tmp); } else if ((tmp=HW_VERSION_MTCDT_0_0),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0) { if(noradio) { struct attribute **ap = mtcdt_platform_attribute_group.attrs; while(1) { if(ap[j] == NULL) { log_info("Did not find radio power attribute. Possible driver fault."); break; } j++; if (is_radio_power_attr_mtcdt(ap[j])) { log_info("Pruning radio feature from mts-io",j); ap[j] = NULL; break; } } } attr_group = &mtcdt_platform_attribute_group; gpio_pins = gpio_pins_mtcdt_0_0; mts_hw_version = MTCDT_0_0; set_buttons(default_buttons); log_info("detected board %s", tmp); } else if (((tmp=HW_VERSION_MTCPM_0_0),(mts_hw_version=MTCPM_0_0),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0) || ((tmp=HW_VERSION_MTCPM_0_1),(mts_hw_version=MTCPM_0_1),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0)) { need_append = 0; current_blength = attr_blength = sizeof mtcpm_platform_attributes; current_blength -= sizeof(struct attribute *); /* Length without terminating NULL */ /* See if we have no radio (never executed for MTCPM_0_0), and if so, prune out the stuff that follows */ if(noradio) { struct attribute **ap = mtcpm_platform_attribute_group.attrs; while(1) { if(ap[j] == NULL) { log_info("Did not find radio reset attribute. Possible driver fault."); break; } j++; if (is_radio_reset_attr_mtcpm(ap[j])) { log_info("Pruning radio feature from mts-io",j); ap[j] = NULL; current_blength = j * sizeof (ap[j]); /* Size without NULL */ attr_blength += sizeof (ap[j]); /* Size of attr array with NULL */ break; } } } if (tmp != HW_VERSION_MTCPM_0_0) { attr_blength += sizeof mtcpm_has_radio_attribute; need_append = 1; } if(DEVICE_CAPA(id_eeprom.capa, CAPA_WIFI)) { attr_blength += sizeof mtcpm_wifi_bt_attributes; need_append = 1; } if(DEVICE_CAPA(id_eeprom.capa, CAPA_GPS)) { attr_blength += sizeof mtcpm_gnss_attributes; need_append = 1; } if (need_append) { freelater = all_attrs = kmalloc(attr_blength,GFP_KERNEL); current_count = current_blength/(sizeof (struct attribute *)); memcpy(all_attrs,mtcpm_platform_attributes,current_blength); if(tmp != HW_VERSION_MTCPM_0_0) { log_info("Adding has_radio to mts-io driver"); memcpy(all_attrs + current_count,mtcpm_has_radio_attribute,sizeof mtcpm_has_radio_attribute); current_count += sizeof mtcpm_has_radio_attribute / (sizeof (struct attribute *)); } if(DEVICE_CAPA(id_eeprom.capa, CAPA_WIFI)) { log_info("Adding WiFi/BT to mts-io driver"); memcpy(all_attrs + current_count,mtcpm_wifi_bt_attributes,sizeof mtcpm_wifi_bt_attributes); current_count += sizeof mtcpm_wifi_bt_attributes / (sizeof (struct attribute *)); } if(DEVICE_CAPA(id_eeprom.capa, CAPA_GPS)) { log_info("Adding GPS to mts-io driver"); attr_blength += sizeof mtcpm_gnss_attributes; memcpy(all_attrs + current_count,mtcpm_gnss_attributes,sizeof mtcpm_gnss_attributes); current_count += sizeof mtcpm_gnss_attributes / (sizeof (struct attribute *)); } all_attrs[current_count] = (struct attribute *)NULL; mtcpm_platform_attribute_group.attrs = all_attrs; } attr_group = &mtcpm_platform_attribute_group; gpio_pins = gpio_pins_mtcpm; set_buttons(default_buttons); log_info("detected board %s", tmp); } else if (strncmp(id_eeprom.product_id, PRODUCT_ID_MT100EOCG, strlen(PRODUCT_ID_MT100EOCG)) == 0) { attr_group = &mt100eocg_platform_attribute_group; gpio_pins = gpio_pins_mt100eocg_0_0; mts_hw_version = MT100EOCG_0_0; set_buttons(default_buttons); log_info("detected board %s", HW_VERSION_MT100EOCG_0_0); } else { int i; mts_hw_version = MTHWUNKNOWN; for(i=0;idev.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) dev_dbg(&mts_io_platform_device->dev, "could not request pin %s (%d) but it could have already been requested under a different pin name", pin->name, ret); } } // Create CPU directory if approprate (only MTCDT3 for now) ret = mts_cpu_dir_create(mts_hw_version); // start general buttons processing init_buttons(); //start supercap monitor worker if SUPERCAP CAPA is true if(DEVICE_CAPA(id_eeprom.capa, CAPA_SUPERCAP)) { init_supercap_worker(); } /* init timers */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0) timer_setup(&radio_reset_timer, radio_reset_timer_callback, 0); timer_setup(&radio_reset_available_timer, radio_reset_available_timer_callback, 0); #else setup_timer(&radio_reset_timer, radio_reset_timer_callback, 0); setup_timer(&radio_reset_available_timer, radio_reset_available_timer_callback, 0); #endif return 0; } static void __exit mts_io_exit(void) { struct gpio_pin *pin; if (DEVICE_CAPA(id_eeprom.capa, CAPA_DOUT)) { spi_unregister_driver(&mts_spi_dout_driver); } if (DEVICE_CAPA(id_eeprom.capa, CAPA_DIN)) { spi_unregister_driver(&mts_spi_din_driver); } spi_unregister_driver(&mts_spi_board_temp_driver); /* delete radio_reset_timer */ del_timer(&radio_reset_timer); /* delete radio_reset_available_timer */ del_timer(&radio_reset_available_timer); for (pin = gpio_pins; *pin->name; pin++) if (pin->capability == 0 || DEVICE_CAPA(id_eeprom.capa,pin->capability)) gpio_free(pin->pin.gpio); cleanup_buttons(); //cleanup supercap monitor worker if SUPERCAP CAPA is true if(DEVICE_CAPA(id_eeprom.capa, CAPA_SUPERCAP)) { cleanup_supercap_worker(); } cleanup(); if (mts_leds) { mts_leds_unregister(); } if (DEVICE_CAPA(id_eeprom.capa, CAPA_LORA) && attr_group_lora) { mts_teardown_lora_port(); } 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");