/* * 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 "config.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" struct platform_device *mts_io_platform_device; EXPORT_SYMBOL(mts_io_platform_device); #ifdef PLATFORM_BUS_SYMLINK struct mts_io_drvdata { bool f_platform_bus_symlink; }; #endif // PLATFORM_BUS_SYMLINK static int mts_io_probe(struct platform_device *pdev) { #ifdef PLATFORM_BUS_SYMLINK struct device *dev = &pdev->dev; const char *symlink_name = kobject_name(&pdev->dev.kobj); struct mts_io_drvdata *mts_io_dd; int res; #endif // PLATFORM_BUS_SYMLINK mts_io_platform_device = pdev; dev_dbg(&pdev->dev,"%s: ENTER\n",__func__); #ifdef PLATFORM_BUS_SYMLINK mts_io_dd = devm_kzalloc(dev, sizeof(*mts_io_dd), GFP_KERNEL); if (!mts_io_dd) { return -ENOMEM; }; mts_io_dd->f_platform_bus_symlink = false; platform_set_drvdata(pdev, mts_io_dd); if (!dev->parent) { dev_info(dev,"Parent device is not found"); } else if (dev->parent == &platform_bus) { dev_info(dev,"Parent device is platform_bus. No need to create symlink"); goto exit; } dev_info(dev,"Creating symlink on platform_bus to %s", symlink_name); res = sysfs_create_link(&platform_bus.kobj, &dev->kobj, symlink_name); if (!res) { mts_io_dd->f_platform_bus_symlink = true; } else { dev_warn(dev,"Failed to create symlink"); } exit: #endif // PLATFORM_BUS_SYMLINK return 0; } static int mts_io_remove(struct platform_device *pdev) { #ifdef PLATFORM_BUS_SYMLINK struct mts_io_drvdata *mts_io_dd = platform_get_drvdata(pdev); const char *symlink_name = kobject_name(&pdev->dev.kobj); if (mts_io_dd && mts_io_dd->f_platform_bus_symlink) { dev_info(&pdev->dev,"Removing %s symlink from platform_bus", symlink_name); sysfs_remove_link(&platform_bus.kobj, symlink_name); } #endif // PLATFORM_BUS_SYMLINK 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; 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; int is_mtr(void); /* * 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. * * If the device is an MTR it always has a Cellular radio. * It is indended to fix wrongly written EEPROM values. * mts_has_radio has to have its own check, because it is exported. * * 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 (is_mtr()) { return 1; } if (!product_id || ! *product_id) return 1; /* No Product ID? */ p = memchr(product_id,'-',len); if (p) { /* Found a hyphen */ dev_dbg(&mts_io_platform_device->dev,"Found hyphen"); p++; if (p >= product_id+len) { dev_dbg(&mts_io_platform_device->dev,"End of string -- hyphen"); return 1; /* Last character was hyphen */ } if (isdigit(*p)) { dev_dbg(&mts_io_platform_device->dev,"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)) { dev_dbg(&mts_io_platform_device->dev,"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) { dev_dbg(&mts_io_platform_device->dev,"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')) { dev_dbg(&mts_io_platform_device->dev,"IN found in 2nd product-id field -- India LoRa Channel plan"); return 0; } if (isdigit(*p)) { dev_dbg(&mts_io_platform_device->dev,"B followed by digit after hyphen - no modem"); return 0; /* B[numeric] is MTR Build number */ } dev_dbg(&mts_io_platform_device->dev,"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 */ dev_dbg(&mts_io_platform_device->dev,"Undefined product-id - has modem"); return 1; /* Product id invalid or empty, so instantiate a radio anyway */ } EXPORT_SYMBOL(mts_has_radio); int is_mtr(void) { if ((strncmp(id_eeprom.hw_version, HW_VERSION_MTR_DASH, strlen(HW_VERSION_MTR_DASH)) == 0)|| (strncmp(id_eeprom.hw_version, HW_VERSION_MTRV1_DASH, strlen(HW_VERSION_MTRV1_DASH)) == 0)) { return 1; } return 0; } /* 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; int reset_time; // Time in ms for reset struct gpio_pin *pin; const char *hw_version; 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) { dev_info(dev,"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)); //dev_info(dev,"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(); dev_info(dev,"radio is reset\n"); pin = gpio_pin_by_attr_name("radio-reset"); if (!pin) { return -ENODEV; } hw_version = mts_get_hw_version(); if ((strncmp(hw_version,HW_VERSION_MTCDT_0_2,strlen(HW_VERSION_MTCDT_0_2)) == 0) || (strncmp(hw_version,HW_VERSION_MTCDTIP_0_1,strlen(HW_VERSION_MTCDTIP_0_1)) == 0)) { dev_dbg(dev,"Using a reset time of 1250mS for USB hub\n"); reset_time = 1250; } else { dev_dbg(dev,"Using a reset time of 250mS\n"); reset_time = 250; } mutex_lock(&mts_io_mutex); // 250/1250ms low reset err = reset_gpio_pin(pin, reset_time, 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 */ //dev_info(&mts_io_platform_device->dev,"radio_reset_available_timer_callback\n"); //dev_info(&mts_io_platform_device->dev,"radio reset available timer is stop = [%d]\n", time_now_secs()); /* zero timings_data_index */ timings_data_index = 0; //dev_info(&mts_io_platform_device->dev,"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 */ //dev_info(&mts_io_platform_device->dev,"radio_reset_timer_callback\n"); //dev_info(&mts_io_platform_device->dev,"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; } //dev_info(&mts_io_platform_device->dev,"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)); //dev_info(&mts_io_platform_device->dev,"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; //dev_info(&mts_io_platform_device->dev,"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)) ){ dev_err(&mts_io_platform_device->dev,"can`t allocate memory\n"); return -EINVAL; } //dev_info(&mts_io_platform_device->dev,"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); //dev_info(&mts_io_platform_device->dev,"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))) { dev_err(&mts_io_platform_device->dev,"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))) { dev_err(&mts_io_platform_device->dev,"radio reset backoffs can`t allocate memory\n"); goto free; } } /* save timings data */ sscanf(pch, "%d", &timings_data[size-1]); } } timings_data_size = size; //dev_info(&mts_io_platform_device->dev,"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)) { dev_err(dev,"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 { dev_err(dev,"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 { dev_err(dev,"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 (is_mtr()) { value = sprintf(buf, "1\n"); // MTRs always have cellular } else { 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 { dev_err(dev,"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. struct attribute **lora_freelater = NULL; // Lora storage to free when driver is unloaded. /* input: version_string - string to match the EEPROM version * input: hw_enum - Enum value that matches version_string * output: found_string - version string if matches, otherwise NULL * output: found_hw - hw_enum if found or MTHWUNKNOWN * output: return value - hw_enum if found or MTHWUNKNOWN */ int found_board(char *version_string, uint8_t hw_enum, char **found_string, uint8_t *found_hw) { if (strncmp(id_eeprom.hw_version, version_string, strlen(version_string)) == 0) { *found_hw = hw_enum; *found_string = version_string; return hw_enum; } else { *found_hw = MTHWUNKNOWN; *found_string = NULL; return MTHWUNKNOWN; } } 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; int attr_lora_blength; // Byte length of base attribute lora array int current_lora_blength; // Current length in bytes of attribute lora array int current_lora_count; // Number of items in lora array struct attribute **all_lora_attrs = NULL; char *hw_version_str; int noradio = 0; // MTCPM-0.0 does not trim off radio-reset, etc. int has_cellular_capaflag = 0; int need_append; int need_lora_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)); dev_info(&mts_io_platform_device->dev,"Platform EEPROM contents loaded"); } else { dev_err(&mts_io_platform_device->dev,"Invalid platform EEPROM length (%d)", fw->size); return -EINVAL; } release_firmware(fw); } else { dev_err(&mts_io_platform_device->dev,"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); } dev_dbg(&mts_io_platform_device->dev,"mts_id_eeprom_load: noradio=%d",noradio); } if (is_mtr()) { noradio = 0; } dev_dbg(&mts_io_platform_device->dev,"mts_id_eeprom_load: noradio=%d",noradio); if (found_board(HW_VERSION_MTCAP_0_0,MTCAP_0_0,&hw_version_str,&mts_hw_version) || found_board(HW_VERSION_MTCAP_0_1,MTCAP_0_1,&hw_version_str,&mts_hw_version) || found_board(HW_VERSION_MTCAP_0_2,MTCAP_0_2,&hw_version_str,&mts_hw_version) || found_board(HW_VERSION_MTCAP_0_3,MTCAP_0_3,&hw_version_str,&mts_hw_version)) { 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) { dev_info(&mts_io_platform_device->dev,"Did not find radio power attribute. Possible driver fault."); break; } j++; if (is_radio_power_attr_mtcap(ap[j])) { dev_info(&mts_io_platform_device->dev,"Pruning radio feature from mts-io"); 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 ((mts_hw_version == MTCAP_0_2) || (mts_hw_version == MTCAP_0_3)) { 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)) { dev_info(&mts_io_platform_device->dev,"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)) { dev_info(&mts_io_platform_device->dev,"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)) { dev_info(&mts_io_platform_device->dev,"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) { dev_info(&mts_io_platform_device->dev,"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; } dev_info(&mts_io_platform_device->dev,"detected board %s", hw_version_str); } else if (found_board(HW_VERSION_MTCAP3_0_0,MTCAP3_0_0,&hw_version_str,&mts_hw_version)) { 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) { dev_info(&mts_io_platform_device->dev,"Did not find radio reset attribute. Possible driver fault."); break; } j++; if (is_radio_power_attr_mtcap3(ap[j])) { dev_info(&mts_io_platform_device->dev,"Pruning radio feature from mts-io"); 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)) { need_lora_append = 0; current_lora_blength = attr_lora_blength = sizeof mtcap3_0_0_lora_attributes; current_lora_blength -= sizeof(struct attribute *); /* Length without terminating NULL */ if (DEVICE_CAPA(id_eeprom.capa, CAPA_LORA_LBT)) { attr_lora_blength += sizeof mtcap3_0_0_lora_lbt_attributes; need_lora_append = 1; } if (need_lora_append) { lora_freelater = all_lora_attrs = kmalloc(attr_lora_blength, GFP_KERNEL); current_lora_count = current_lora_blength/(sizeof (struct attribute *)); memcpy(all_lora_attrs, mtcap3_0_0_lora_attributes, current_lora_blength); if (DEVICE_CAPA (id_eeprom.capa, CAPA_LORA_LBT)) { dev_info(&mts_io_platform_device->dev,"Adding lora-lbt to mts-io driver"); memcpy(all_lora_attrs + current_lora_count, mtcap3_0_0_lora_lbt_attributes, sizeof mtcap3_0_0_lora_lbt_attributes); current_lora_count += sizeof mtcap3_0_0_lora_lbt_attributes / (sizeof (struct attribute *)); } all_lora_attrs[current_lora_count] = (struct attribute *)NULL; mtcap3_0_0_lora_attribute_group.attrs = all_lora_attrs; } 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; dev_info(&mts_io_platform_device->dev,"detected board %s", HW_VERSION_MTCAP3_0_0); } else if (found_board(HW_VERSION_MTRE,MTRE_0_0,&hw_version_str,&mts_hw_version)) { 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; dev_info(&mts_io_platform_device->dev,"detected board %s", HW_VERSION_MTRE); } else if (found_board(HW_VERSION_MTR_0_0,MTR_0_0,&hw_version_str,&mts_hw_version)) { attr_group = &mtr_platform_attribute_group; gpio_pins = gpio_pins_mtr_0_0; set_buttons(default_buttons); mts_hw_version = MTR_0_0; dev_info(&mts_io_platform_device->dev,"detected board %s", HW_VERSION_MTR_0_0); } else if (found_board(HW_VERSION_MTR_0_1,MTR_0_1,&hw_version_str,&mts_hw_version)) { attr_group = &mtr_platform_attribute_group; gpio_pins = gpio_pins_mtr_0_1; set_buttons(default_buttons); mts_hw_version = MTR_0_1; dev_info(&mts_io_platform_device->dev,"detected board %s", HW_VERSION_MTR_0_1); } else if (found_board(HW_VERSION_MTRV1_0_0,MTRV1_0_0,&hw_version_str,&mts_hw_version)) { attr_group = &mtr_platform_attribute_group; gpio_pins = gpio_pins_mtrv1_0_0; set_buttons(default_buttons); mts_hw_version = MTRV1_0_0; dev_info(&mts_io_platform_device->dev,"detected board %s", HW_VERSION_MTRV1_0_0); } else if (found_board(HW_VERSION_MTRV1_0_1,MTRV1_0_1,&hw_version_str,&mts_hw_version)) { 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; dev_info(&mts_io_platform_device->dev,"detected board %s", HW_VERSION_MTRV1_0_1); } else if (found_board(HW_VERSION_MTRV1_0_2,MTRV1_0_2,&hw_version_str,&mts_hw_version)) { 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; dev_info(&mts_io_platform_device->dev,"detected board %s", HW_VERSION_MTRV1_0_2); } else if (found_board(HW_VERSION_MTRV1_0_3,MTRV1_0_3,&hw_version_str,&mts_hw_version)) { 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; dev_info(&mts_io_platform_device->dev,"detected board %s", HW_VERSION_MTRV1_0_3); } else if (found_board(HW_VERSION_MTRV1_0_4,MTRV1_0_4,&hw_version_str,&mts_hw_version)) { 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); dev_info(&mts_io_platform_device->dev,"detected board %s", HW_VERSION_MTRV1_0_4); } else if (found_board(HW_VERSION_MTHS_0_0,MTHS_0_0,&hw_version_str,&mts_hw_version)) { 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; dev_info(&mts_io_platform_device->dev,"detected board %s", HW_VERSION_MTHS_0_0); } else if (found_board(HW_VERSION_MTHS_0_1,MTHS_0_1,&hw_version_str,&mts_hw_version)) { 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; dev_info(&mts_io_platform_device->dev,"detected board %s", HW_VERSION_MTHS_0_1); } else if (found_board(HW_VERSION_MTCDT_0_1,MTCDT_0_1,&hw_version_str,&mts_hw_version) || found_board(HW_VERSION_MTCDTIP_0_0,MTCDTIP_0_0,&hw_version_str,&mts_hw_version)) { 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) { dev_info(&mts_io_platform_device->dev,"Did not find radio power attribute. Possible driver fault."); break; } j++; if (is_radio_power_attr_mtcdt(ap[j])) { dev_info(&mts_io_platform_device->dev,"Pruning radio feature from mts-io"); 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)) { dev_info(&mts_io_platform_device->dev,"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)) { dev_info(&mts_io_platform_device->dev,"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); dev_info(&mts_io_platform_device->dev,"detected board %s", hw_version_str); } else if (found_board(HW_VERSION_MTCDT_0_2,MTCDT_0_2,&hw_version_str,&mts_hw_version) || found_board(HW_VERSION_MTCDTIP_0_1,MTCDTIP_0_1,&hw_version_str,&mts_hw_version)) { 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) { dev_info(&mts_io_platform_device->dev,"Did not find radio power attribute. Possible driver fault."); break; } j++; if (is_radio_power_attr_mtcdt(ap[j])) { dev_info(&mts_io_platform_device->dev,"Pruning radio feature from mts-io"); 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)) { dev_info(&mts_io_platform_device->dev,"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)) { dev_info(&mts_io_platform_device->dev,"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); dev_info(&mts_io_platform_device->dev,"detected board %s", hw_version_str); } else if (found_board(HW_VERSION_MTCDTIPHP_0_0,MTCDTIPHP_0_0,&hw_version_str,&mts_hw_version)) { 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) { dev_info(&mts_io_platform_device->dev,"Did not find radio power attribute. Possible driver fault."); break; } j++; if (is_radio_power_attr_mtcdt(ap[j])) { dev_info(&mts_io_platform_device->dev,"Pruning radio feature from mts-io"); 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)) { dev_info(&mts_io_platform_device->dev,"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; dev_info(&mts_io_platform_device->dev,"detected board %s", hw_version_str); } else if (found_board(HW_VERSION_MTCDT_0_0,MTCDT_0_0,&hw_version_str,&mts_hw_version)) { if(noradio) { struct attribute **ap = mtcdt_platform_attribute_group.attrs; while(1) { if(ap[j] == NULL) { dev_info(&mts_io_platform_device->dev,"Did not find radio power attribute. Possible driver fault."); break; } j++; if (is_radio_power_attr_mtcdt(ap[j])) { dev_info(&mts_io_platform_device->dev,"Pruning radio feature from mts-io"); 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); dev_info(&mts_io_platform_device->dev,"detected board %s", hw_version_str); } else if (found_board(HW_VERSION_MTCPM_0_0,MTCPM_0_0,&hw_version_str,&mts_hw_version) || found_board(HW_VERSION_MTCPM_0_1,MTCPM_0_1,&hw_version_str,&mts_hw_version) || found_board(HW_VERSION_MTCPM_0_2,MTCPM_0_2,&hw_version_str,&mts_hw_version)) { 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) { dev_info(&mts_io_platform_device->dev,"Did not find radio reset attribute. Possible driver fault."); break; } j++; if (is_radio_reset_attr_mtcpm(ap[j])) { dev_info(&mts_io_platform_device->dev,"Pruning radio feature from mts-io"); 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 (strncmp(hw_version_str,HW_VERSION_MTCPM_DASH,strlen(HW_VERSION_MTCPM_DASH))!=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 (strncmp(hw_version_str,HW_VERSION_MTCPM_DASH,strlen(HW_VERSION_MTCPM_DASH))!=0) { dev_info(&mts_io_platform_device->dev,"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)) { dev_info(&mts_io_platform_device->dev,"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)) { dev_info(&mts_io_platform_device->dev,"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); mts_leds = mtcpmhs_leds; dev_info(&mts_io_platform_device->dev,"detected board %s", hw_version_str); } else if (found_board(HW_VERSION_MT100EOCG_0_0,MT100EOCG_0_0,&hw_version_str,&mts_hw_version)) { attr_group = &mt100eocg_platform_attribute_group; gpio_pins = gpio_pins_mt100eocg_0_0; mts_hw_version = MT100EOCG_0_0; set_buttons(default_buttons); dev_info(&mts_io_platform_device->dev,"detected board %s", HW_VERSION_MT100EOCG_0_0); } else { int i; mts_hw_version = MTHWUNKNOWN; for(i=0;idev,"Unsupported EEPROM settings or device"); if(i) dev_alert(&mts_io_platform_device->dev,"Found unsupported EEPROM HW_VERSION: %.*s",i,id_eeprom.hw_version); dev_alert(&mts_io_platform_device->dev,"Check log for HW_VERSION dump"); print_hex_dump_bytes("HW_VERSION: ", DUMP_PREFIX_OFFSET, id_eeprom.hw_version, sizeof id_eeprom.hw_version); return -ENODEV; } dev_info(&mts_io_platform_device->dev,"EEPROM-Layout-Version: %u",id_eeprom.eeprom_layout_version); dev_info(&mts_io_platform_device->dev,"sizeof: %lu", (unsigned long) sizeof(struct mts_id_eeprom_layout)); dev_info(&mts_io_platform_device->dev,"vendor-id: %.32s", id_eeprom.vendor_id); dev_info(&mts_io_platform_device->dev,"product-id: %.32s", id_eeprom.product_id); dev_info(&mts_io_platform_device->dev,"device-id: %.32s", id_eeprom.device_id); dev_info(&mts_io_platform_device->dev,"hw-version: %.32s", id_eeprom.hw_version); dev_info(&mts_io_platform_device->dev,"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]); dev_info(&mts_io_platform_device->dev,"imei: %.32s", id_eeprom.imei); dev_info(&mts_io_platform_device->dev,"capa-gps: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_GPS) ? "yes" : "no"); dev_info(&mts_io_platform_device->dev,"capa-din: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_DIN) ? "yes" : "no"); dev_info(&mts_io_platform_device->dev,"capa-dout: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_DOUT) ? "yes" : "no"); dev_info(&mts_io_platform_device->dev,"capa-adc: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_ADC) ? "yes" : "no"); dev_info(&mts_io_platform_device->dev,"capa-wifi: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_WIFI) ? "yes" : "no"); dev_info(&mts_io_platform_device->dev,"capa-bluetooth: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_BLUETOOTH) ? "yes" : "no"); if ((mts_hw_version != MTCPM_0_0) && (mts_hw_version != MTCPM_0_1) && (mts_hw_version != MTCPM_0_2)) { /* Moved to mtcdt3b driver in MTCDT3 baseboard hardware */ dev_info(&mts_io_platform_device->dev,"capa-lora: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_LORA) ? "yes" : "no"); dev_info(&mts_io_platform_device->dev,"capa-lora-lbt: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_LORA_LBT) ? "yes" : "no"); } dev_info(&mts_io_platform_device->dev,"capa-battery: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_BATTERY) ? "yes" : "no"); dev_info(&mts_io_platform_device->dev,"capa-supercap: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_SUPERCAP) ? "yes" : "no"); if(has_cellular_capaflag) dev_info(&mts_io_platform_device->dev,"capa-cellular: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_CELLULAR) ? "yes" : "no"); dev_info(&mts_io_platform_device->dev,"capa-user-data-encryption: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_USER_DATA_ENCRYPTION) ? "yes" : "no"); if (DEVICE_CAPA(id_eeprom.capa, CAPA_BLUETOOTH)) { dev_info(&mts_io_platform_device->dev,"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)) { dev_info(&mts_io_platform_device->dev,"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]); } dev_info(&mts_io_platform_device->dev,"uuid: %s", (char*)buf); if (DEVICE_CAPA(id_eeprom.capa, CAPA_LORA)) { dev_info(&mts_io_platform_device->dev,"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]); dev_info(&mts_io_platform_device->dev,"lora-product-id: %.32s", id_eeprom.lora_product_id); dev_info(&mts_io_platform_device->dev,"lora-hw-version: %.32s", id_eeprom.lora_hw_version); } #ifdef MTRE dev_info(&mts_io_platform_device->dev,"oem-string1: %.32s", id_eeprom.oem_string1); dev_info(&mts_io_platform_device->dev,"oem-string2: %.32s", id_eeprom.oem_string2); #endif return 0; } static void cleanup(void) { dev_info(&mts_io_platform_device->dev,"mts-io: cleaning up...."); if (mts_io_platform_device != NULL && attr_group != NULL) { sysfs_remove_group(&mts_io_platform_device->dev.kobj, attr_group); } platform_driver_unregister(&mts_io_driver); if(freelater) { kfree(freelater); freelater = NULL; } if(lora_freelater) { kfree(lora_freelater); lora_freelater = NULL; } mts_capab_dir_delete(); mts_cpu_dir_delete(); } static int __init mts_io_init(void) { struct gpio_pin *pin; int ret; pr_info("mts-io init: " VERSION); /* We do a platform_driver_register to do a probe * of device tree and set the pinctrl/gpio settings. */ ret = platform_driver_register(&mts_io_driver); if (ret) { dev_err(&mts_io_platform_device->dev,"mts-io: probe failed: %d\n", ret); cleanup(); return ret; } /* Without a platform device our EEPROM load will * not work, and we cannot continue. */ if (mts_io_platform_device == NULL) { dev_err(&mts_io_platform_device->dev,"mts-io: probe failed, possible bad device tree\n"); return -ENODEV; } /* request_firmware() requires a device, so call after device allocated */ ret = mts_id_eeprom_load(); if (ret != 0) { cleanup(); return ret; } if (DEVICE_CAPA(id_eeprom.capa, CAPA_DOUT)) { ret = spi_register_driver(&mts_spi_dout_driver); if (ret) { dev_err(&mts_io_platform_device->dev,"mts-io:mts-io-dout: probe failed: %d\n", ret); } } if (DEVICE_CAPA(id_eeprom.capa, CAPA_DIN)) { ret = spi_register_driver(&mts_spi_din_driver); if (ret) { dev_err(&mts_io_platform_device->dev,"mts-io:mts-io-din: probe failed: %d\n", ret); } } ret = spi_register_driver(&mts_spi_board_temp_driver); if (ret) { dev_err(&mts_io_platform_device->dev,"mts-io:mts-io-board-temp: probe failed: %d\n", ret); } if (DEVICE_CAPA(id_eeprom.capa, CAPA_LORA) && attr_group_lora) { mts_load_lora_port(); } if (mts_leds) { mts_leds_register(); } ret = mts_capab_dir_create(mts_hw_version); 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)) { if (pin->do_gpio_desc) { dev_dbg(&mts_io_platform_device->dev,"Request name:%s label: %s\n", pin->name, pin->pin.label); pin->desc = devm_gpiod_get_optional(&mts_io_platform_device->dev, pin->name, pin->pin.flags); if (IS_ERR(pin->desc)) { dev_err(&mts_io_platform_device->dev, "%s: Could not get gpio %s: Error: %ld\n", __func__, pin->name, PTR_ERR(pin->desc)); } else { if (pin->desc == NULL) dev_dbg(&mts_io_platform_device->dev,"gpio_desc is null for name: %s, label: %s\n", pin->name, pin->pin.label); else dev_dbg(&mts_io_platform_device->dev,"Found gpio %s\n", pin->name); } } else { dev_dbg(&mts_io_platform_device->dev,"Request name:%s label: %s pin: %d\n", pin->name, pin->pin.label, pin->pin.gpio); 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\n", pin->name, ret); // Example: radio-reset, radio-power mapped to the same pin. } } } // 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; } #define GPIO_ATTR_MAX 256 static void __exit mts_io_exit(void) { int old_pin[GPIO_ATTR_MAX]; int old_last = 0; int i,found; struct gpio_pin *pin; struct device *dev = &mts_io_platform_device->dev; 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)) { if (pin->do_gpio_desc) { dev_dbg(dev,"Kernel removes when module is unloaded: GPIO descriptor case: pin->name=%s\n", pin->name); } else { // TODO: Remove this code when the last pin is converted to the new GPIO model dev_dbg(dev,"Deprecated GPIO pin # case: pin->name=%s\n", pin->name); found = 0; for(i=0; ipin.gpio == old_pin[i]) { dev_dbg(dev,"Deprecated GPIO pin #, skipping free of: pin->name=%s\n", pin->name); found = 1; break; } } if (!found) { old_pin[old_last] = pin->pin.gpio; gpio_free(pin->pin.gpio); old_last++; if(old_last > GPIO_ATTR_MAX) panic("MTS-IO list of gpio pin numbers is full\n"); } } // else Old GPIO model } // GPIO pin exists } // End for gpio_pins cleanup_buttons(); //cleanup supercap monitor worker if SUPERCAP CAPA is true if(DEVICE_CAPA(id_eeprom.capa, CAPA_SUPERCAP)) { cleanup_supercap_worker(); } cleanup(); // platform device does not exist if (mts_leds) { mts_leds_unregister(); } if (DEVICE_CAPA(id_eeprom.capa, CAPA_LORA) && attr_group_lora) { mts_teardown_lora_port(); } pr_info("mts-io exiting dev"); } module_init(mts_io_init); module_exit(mts_io_exit); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_VERSION(VERSION); MODULE_LICENSE("GPL");