/* * 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_module.h" #include "mts_io.h" /* #include "mtac.h" */ #include "buttons.h" #define PLATFORM_NAME "mts-io" #define LED_LS_CONTROLLABLE 0 int mtsio_reread_eeprom = 0; module_param(mtsio_reread_eeprom, int, S_IRUSR | S_IRGRP | S_IROTH); MODULE_PARM_DESC(mtsio_reread_eeprom, "Non-zero means re-read EEPROM"); /* on-board EEPROM */ extern uint8_t mts_id_eeprom[512]; static struct mts_id_eeprom_layout id_eeprom; 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 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) bool sent_extra_long = false; /* * 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); 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; 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" /* Start of mtcdt.c */ /* * Within a struct gpio_pin, there is only one * occurance of each pin, so there is only one * pin label set for each gpio pin. */ static struct gpio_pin gpio_pins_mtcdt_0_0[] = { { .name = "RADIO_RESET", .pin = { .gpio = AT91_PIN_PC3, .flags = GPIOF_OUT_INIT_HIGH, .label = "radio-reset", }, }, { .name = "RADIO_RESET", .pin = { .gpio = AT91_PIN_PC3, .flags = GPIOF_OUT_INIT_HIGH, .label = "radio-power", }, }, { .name = "DEVICE_RESET", .pin = { .gpio = AT91_PIN_PC2, .flags = GPIOF_IN, .label = "reset", }, .active_low = 1, }, { .name = "ETH_RESET", .pin = { .gpio = AT91_PIN_PC4, .flags = GPIOF_OUT_INIT_HIGH, .label = "eth-reset", } }, { .name = "LS_LED", /* LED7 */ .pin = { .gpio = AT91_PIN_PA14, #if LED_LS_CONTROLLABLE .flags = GPIOF_OUT_INIT_HIGH, #else .flags = GPIOF_IN, #endif .label = "led-ls", }, .active_low = 1, }, { .name = "STATUS_LED", /* LED2 */ .pin = { .gpio = AT91_PIN_PA24, .flags = GPIOF_OUT_INIT_LOW, .label = "led-status", }, .active_low = 1, }, { .name = "LED5", .pin = { .gpio = AT91_PIN_PA25, .flags = GPIOF_OUT_INIT_HIGH, .label = "led-cd", }, .active_low = 1, }, { .name = "LED5", .pin = { .gpio = AT91_PIN_PA25, .flags = GPIOF_OUT_INIT_HIGH, .label = "led-a", }, .active_low = 1, }, { .name = "LED1", .pin = { .gpio = AT91_PIN_PA26, .flags = GPIOF_OUT_INIT_HIGH, .label = "led-sig1", }, .active_low = 1, }, { .name = "LED1", .pin = { .gpio = AT91_PIN_PA26, .flags = GPIOF_OUT_INIT_HIGH, .label = "led-b", }, .active_low = 1, }, { .name = "LED4", .pin = { .gpio = AT91_PIN_PA27, .flags = GPIOF_OUT_INIT_HIGH, .label = "led-sig2", }, .active_low = 1, }, { .name = "LED4", .pin = { .gpio = AT91_PIN_PA27, .flags = GPIOF_OUT_INIT_HIGH, .label = "led-c", }, .active_low = 1, }, { .name = "LED3", .pin = { .gpio = AT91_PIN_PA28, .flags = GPIOF_OUT_INIT_HIGH, .label = "led-sig3", }, .active_low = 1, }, { .name = "LED3", .pin = { .gpio = AT91_PIN_PA28, .flags = GPIOF_OUT_INIT_HIGH, .label = "led-d", }, .active_low = 1, }, }; static struct gpio_pin gpio_pins_mtcdt_0_1[] = { { .name = "RADIO_RESET", .pin = { .gpio = AT91_PIN_PC3, .flags = GPIOF_OUT_INIT_HIGH, .label = "radio-reset", }, }, { .name = "RADIO_RESET", .pin = { .gpio = AT91_PIN_PC3, .flags = GPIOF_OUT_INIT_HIGH, .label = "radio-power", }, }, { .name = "DEVICE_RESET", .pin = { .gpio = AT91_PIN_PC2, .flags = GPIOF_IN, .label = "reset", }, .active_low = 1, }, { .name = "LS_LED", /* LED7 */ .pin = { .gpio = AT91_PIN_PA14, #if LED_LS_CONTROLLABLE .flags = GPIOF_OUT_INIT_HIGH, #else .flags = GPIOF_IN, #endif .label = "led-ls", }, .active_low = 1, }, { .name = "STATUS_LED", /* LED2 */ .pin = { .gpio = AT91_PIN_PA24, .flags = GPIOF_OUT_INIT_LOW, .label = "led-status", }, .active_low = 1, }, { .name = "LED5", .pin = { .gpio = AT91_PIN_PA25, .flags = GPIOF_OUT_INIT_HIGH, .label = "led-cd", }, .active_low = 1, }, { .name = "LED5", .pin = { .gpio = AT91_PIN_PA25, .flags = GPIOF_OUT_INIT_HIGH, .label = "led-a", }, .active_low = 1, }, { .name = "LED1", .pin = { .gpio = AT91_PIN_PA26, .flags = GPIOF_OUT_INIT_HIGH, .label = "led-sig1", }, .active_low = 1, }, { .name = "LED1", .pin = { .gpio = AT91_PIN_PA26, .flags = GPIOF_OUT_INIT_HIGH, .label = "led-b", }, .active_low = 1, }, { .name = "LED4", .pin = { .gpio = AT91_PIN_PA27, .flags = GPIOF_OUT_INIT_HIGH, .label = "led-sig2", }, .active_low = 1, }, { .name = "LED4", .pin = { .gpio = AT91_PIN_PA27, .flags = GPIOF_OUT_INIT_HIGH, .label = "led-c", }, .active_low = 1, }, { .name = "LED3", .pin = { .gpio = AT91_PIN_PA28, .flags = GPIOF_OUT_INIT_HIGH, .label = "led-sig3", }, .active_low = 1, }, { .name = "LED3", .pin = { .gpio = AT91_PIN_PA28, .flags = GPIOF_OUT_INIT_HIGH, .label = "led-d", }, .active_low = 1, }, { .name = "WIFI_BT_ULPWKUP", .pin = { .gpio = AT91_PIN_PA0, .flags = GPIOF_IN, .label = "wifi-bt-ulpwkup", }, .capability = CAPA_WIFI, }, { .name = "WIFI_BT_LPWKUP", .pin = { .gpio = AT91_PIN_PA6, .flags = GPIOF_IN, .label = "wifi-bt-lpwkup", }, .capability = CAPA_WIFI, }, { .name = "WIFI_BT_INT", .pin = { .gpio = AT91_PIN_PB11, .flags = GPIOF_IN, .label = "wifi-bt-int", }, .capability = CAPA_WIFI, }, { .name = "WIFI_BT_RESET", .pin = { .gpio = AT91_PIN_PD14, .flags = GPIOF_OUT_INIT_HIGH, .label = "wifi-bt-reset", }, .capability = CAPA_WIFI, }, { .name = "GNSS_RESET", .pin = { .gpio = AT91_PIN_PD15, .flags = GPIOF_OUT_INIT_HIGH, .label = "gnss-reset", }, .capability = CAPA_GPS, }, { .name = "SECURE_RESET", .pin = { .gpio = AT91_PIN_PD16, .flags = GPIOF_OUT_INIT_HIGH, .label = "secure-reset", } }, { .name = "MTQ_RESET", .pin = { .gpio = AT91_PIN_PD17, .flags = GPIOF_OUT_INIT_HIGH, .label = "mtq-reset", } }, { .name = "USBHUB_RESET", .pin = { .gpio = AT91_PIN_PD18, .flags = GPIOF_OUT_INIT_HIGH, .label = "usbhub-reset", } }, { .name = "GNSS_INT", .pin = { .gpio = AT91_PIN_PD19, .flags = GPIOF_OUT_INIT_HIGH, .label = "gnss-int", }, .capability = CAPA_GPS, }, { .name = "WIFI_BT_LPMODE", .pin = { .gpio = AT91_PIN_PD20, .flags = GPIOF_IN, .label = "wifi-bt-lpmode", }, .capability = CAPA_WIFI, }, { }, }; static DEVICE_ATTR_MTS(dev_attr_wifi_bt_lpwkup, "wifi-bt-lpwkup", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_wifi_bt_ulpwkup, "wifi-bt-ulpwkup", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_wifi_bt_reset, "wifi-bt-reset", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_RO_MTS(dev_attr_wifi_bt_lpmode, "wifi-bt-lpmode", mts_attr_show_gpio_pin); static DEVICE_ATTR_RO_MTS(dev_attr_wifi_bt_int, "wifi-bt-int", mts_attr_show_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_gnss_reset, "gnss-reset", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_usbhub_reset, "usbhub-reset", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_eth_reset, "eth-reset", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_gnss_int, "gnss-int", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static struct attribute *mtcdt_platform_attributes[] = { &dev_attr_vendor_id.attr, &dev_attr_product_id.attr, &dev_attr_device_id.attr, &dev_attr_uuid.attr, &dev_attr_hw_version.attr, &dev_attr_imei.attr, &dev_attr_eth_mac.attr, &dev_attr_has_radio.attr, &dev_attr_reset.attr, &dev_attr_reset_monitor.attr, &dev_attr_reset_monitor_intervals.attr, &dev_attr_led_status.attr, &dev_attr_led_cd_gpio.attr, &dev_attr_led_sig1_gpio.attr, &dev_attr_led_sig2_gpio.attr, &dev_attr_led_sig3_gpio.attr, &dev_attr_led_a_gpio.attr, &dev_attr_led_b_gpio.attr, &dev_attr_led_c_gpio.attr, &dev_attr_led_d_gpio.attr, &dev_attr_eth_reset.attr, &dev_attr_radio_power.attr, &dev_attr_radio_reset.attr, &dev_attr_radio_reset_backoffs.attr, &dev_attr_radio_reset_backoff_index.attr, &dev_attr_radio_reset_backoff_seconds.attr, NULL, }; static struct attribute *mtcdt_0_1_platform_attributes[] = { &dev_attr_vendor_id.attr, &dev_attr_product_id.attr, &dev_attr_device_id.attr, &dev_attr_uuid.attr, &dev_attr_hw_version.attr, &dev_attr_imei.attr, &dev_attr_eth_mac.attr, &dev_attr_has_radio.attr, &dev_attr_reset.attr, &dev_attr_reset_monitor.attr, &dev_attr_reset_monitor_intervals.attr, &dev_attr_led_status.attr, &dev_attr_led_cd_gpio.attr, &dev_attr_led_sig1_gpio.attr, &dev_attr_led_sig2_gpio.attr, &dev_attr_led_sig3_gpio.attr, &dev_attr_led_a_gpio.attr, &dev_attr_led_b_gpio.attr, &dev_attr_led_c_gpio.attr, &dev_attr_led_d_gpio.attr, &dev_attr_usbhub_reset.attr, &dev_attr_eth_reset.attr, // radio feature is last to be able to // easily remove radio. // is_radio_power_attr_mtcdt() searches // for this for truncation. &dev_attr_radio_power.attr, /* Must be first radio attribute */ &dev_attr_radio_reset.attr, &dev_attr_radio_reset_backoffs.attr, &dev_attr_radio_reset_backoff_index.attr, &dev_attr_radio_reset_backoff_seconds.attr, NULL, }; static struct attribute *mtcdt_0_1_wifi_bt_attributes[] = { &dev_attr_wifi_bt_lpwkup.attr, &dev_attr_wifi_bt_ulpwkup.attr, &dev_attr_wifi_bt_reset.attr, &dev_attr_wifi_bt_lpmode.attr, &dev_attr_wifi_bt_int.attr, }; static struct attribute *mtcdt_0_1_gnss_attributes[] = { &dev_attr_gnss_reset.attr, &dev_attr_gnss_int.attr, }; static struct attribute_group mtcdt_platform_attribute_group = { .attrs = mtcdt_platform_attributes }; static struct attribute_group mtcdt_0_1_platform_attribute_group = { .attrs = mtcdt_0_1_platform_attributes }; static int is_radio_power_attr_mtcdt(struct attribute *attr) { return (attr == &dev_attr_radio_power.attr); } /* End of mths.c */ #include "mtcap.c" #include "mtr.c" #include "mths.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; //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); } 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; } if (DEVICE_CAPA(id_eeprom.capa, CAPA_LORA) && attr_group_lora) { mts_load_lora_port(); } 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) { struct gpio_pin *pin; /* 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(); 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");