/* * 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" #include "buttons.h" #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; 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" /* 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) 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; /* * This function takes the product_id and tries to check * for a modem. If there is an error, assume there is a * modem. 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, 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 or is followed * by a numeric, we have no modem. * All other cases, we have a modem. */ static int has_radio(const char *product_id, size_t len) { char *p; 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 */ } if (*p != 'B') { log_debug("Found neither B nor digit after hypen"); return 1; /* Modem starting with a letter, but not B */ } /* Found a B */ p++; if (p >= product_id+len) { log_debug("B at end of product-id string"); return 1; /* Last character was B */ } 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 */ } /* 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; } 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 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, "has-radio") == 0) { value = sprintf(buf, "%1d\n", has_radio(id_eeprom.product_id,sizeof 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-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-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_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); 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; // Add other radios as needed. return rc; } /* include on-board lora peripheral */ #include "mts_lora.c" /* include per-device pins and attributes */ #include "mtcdt.c" #include "mtcap.c" #include "mtr.c" #include "mths.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" #include "mtac_pulse.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 if (strstr(ap_eeprom[port_index].product_id, PRODUCT_ID_MTAC_PULSE)) { if (! set_pulse_info(port_info[port_index])) { log_error("failed to set up pulse 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(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; //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; } noradio = ! has_radio(id_eeprom.product_id,sizeof id_eeprom.product_id); log_debug("mts_id_eeprom: noradio=%d",noradio); if (((tmp=HW_VERSION_MTCAP_0_0),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0) || ((tmp=HW_VERSION_MTCAP_0_1),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0)) { /* 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; break; } } } 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 (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_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 (((tmp=HW_VERSION_MTCDT_0_1),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 0) || ((tmp=HW_VERSION_MTCDTIP_0_0),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 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 = attr_blength = j * sizeof (ap[j]); break; } } } 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 (current_blength+(sizeof(struct attribute *)) != attr_blength) { 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_MTCDTIPHP_0_0),strncmp(id_eeprom.hw_version, tmp, strlen(tmp)) == 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 = attr_blength = j * sizeof (ap[j]); break; } } } if(DEVICE_CAPA(id_eeprom.capa, CAPA_WIFI)) { attr_blength += sizeof mtcdt_0_1_wifi_bt_attributes; } if (current_blength+(sizeof(struct attribute *)) != attr_blength) { 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); log_info("detected board %s", tmp); } else { 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", 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 general buttons processing init_buttons(); /* 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); cleanup_buttons(); 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");