/* * MTS-IO Controller * * Copyright (C) 2014 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 "mts_io.h" #define DRIVER_VERSION "v1.1.1" #define DRIVER_AUTHOR "James Maki " #define DRIVER_DESC "MTS-IO Controller" #define DRIVER_NAME "mts-io" #define PLATFORM_NAME "mts-io" #define LED_LS_CONTROLLABLE 0 /* on-board EEPROM */ extern uint8_t mts_id_eeprom[512]; static struct mts_id_eeprom_layout id_eeprom; // NUM_AP should be defined from the board code // it should be set to the value of CONFIG_MTS_NUM_ACCESSORY_PORTS // arch/arm/mach-at91/board-dt-sam9.c // if it is 0 or undefined, there is no accessory card support on this HW #ifdef CONFIG_MTS_NUM_ACCESSORY_PORTS #ifndef NUM_AP #define NUM_AP CONFIG_MTS_NUM_ACCESSORY_PORTS #endif #else #define NUM_AP 0 #endif #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]; #endif static struct ap_info* port_info[NUM_AP]; static struct platform_device *mts_io_platform_device; static struct attribute_group *attr_group; static struct gpio_pin *gpio_pins; static DEFINE_MUTEX(mts_io_mutex); /* generic GPIO support */ #include "gpio.c" /* accessory card support */ #include "mtac.c" #include "mtac_gpiob.c" #include "mtac_mfser.c" #include "mtac_eth.c" #include "mtac_lora.c" /* reset button handling */ #define RESET_CHECK_PER_SEC 8 #define RESET_INTERVAL (HZ / RESET_CHECK_PER_SEC) #define RESET_HOLD_COUNT (RESET_CHECK_PER_SEC * 3) #define RESET_LONG_HOLD_COUNT (RESET_CHECK_PER_SEC * 30) static pid_t reset_pid = -1; static pid_t reset_count = 0; bool sent_extra_long = false; static int reset_short_signal = SIGUSR1; static int reset_long_signal = SIGUSR2; static int reset_extra_long_signal = SIGHUP; static int reset_short_interval = RESET_HOLD_COUNT; static int reset_long_interval = RESET_LONG_HOLD_COUNT; static void reset_callback(struct work_struct *ignored); static DECLARE_DELAYED_WORK(reset_work, reset_callback); static void reset_callback(struct work_struct *ignored) { struct gpio_pin *pin; int reset_pressed = 0; struct pid *vpid = NULL; mutex_lock(&mts_io_mutex); pin = gpio_pin_by_name("DEVICE_RESET"); if (pin) { reset_pressed = !gpio_get_value(pin->pin.gpio); } if (reset_pid > 0) { vpid = find_vpid(reset_pid); } if (vpid) { if (reset_pressed) { reset_count++; } else { //Reset button has not been pressed if (reset_count > 0 && reset_count < reset_short_interval) { kill_pid(vpid, reset_short_signal, 1); } else if (reset_count >= reset_short_interval && reset_count < reset_long_interval) { kill_pid(vpid, reset_long_signal, 1); } reset_count = 0; sent_extra_long = false; } if (reset_count >= reset_long_interval && ! sent_extra_long) { kill_pid(vpid, reset_extra_long_signal, 1); sent_extra_long = true; } } else { reset_count = 0; } mutex_unlock(&mts_io_mutex); schedule_delayed_work(&reset_work, RESET_INTERVAL); } static ssize_t mts_attr_show_reset_monitor_intervals(struct device *dev, struct device_attribute *attr, char *buf) { int ret; mutex_lock(&mts_io_mutex); ret = sprintf(buf, "%d %d\n", reset_short_interval / RESET_CHECK_PER_SEC, reset_long_interval / RESET_CHECK_PER_SEC); mutex_unlock(&mts_io_mutex); return ret; } static ssize_t mts_attr_store_reset_monitor_intervals(struct device *dev, struct device_attribute *attr, char *buf, size_t count) { int short_int; int long_int; if (sscanf(buf, "%i %i", &short_int, &long_int) != 2) { return -EINVAL; } mutex_lock(&mts_io_mutex); reset_short_interval = short_int * RESET_CHECK_PER_SEC; reset_long_interval = long_int * RESET_CHECK_PER_SEC; mutex_unlock(&mts_io_mutex); return count; } static DEVICE_ATTR_MTS(dev_attr_reset_monitor_intervals, "reset-monitor-intervals", mts_attr_show_reset_monitor_intervals, mts_attr_store_reset_monitor_intervals); static ssize_t mts_attr_show_reset_monitor(struct device *dev, struct device_attribute *attr, char *buf) { int ret; mutex_lock(&mts_io_mutex); ret = sprintf(buf, "%d %d %d %d\n", reset_pid, reset_short_signal, reset_long_signal, reset_extra_long_signal); mutex_unlock(&mts_io_mutex); return ret; } static ssize_t mts_attr_store_reset_monitor(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { pid_t pid; int short_signal; int long_signal; int extra_long_signal; int result = sscanf(buf, "%i %i %i %i", &pid, &short_signal, &long_signal, &extra_long_signal); if (result < 3 || result > 4) { return -EINVAL; } if(result == 3) { mutex_lock(&mts_io_mutex); reset_pid = pid; reset_short_signal = short_signal; reset_long_signal = long_signal; mutex_unlock(&mts_io_mutex); } else { mutex_lock(&mts_io_mutex); reset_pid = pid; reset_short_signal = short_signal; reset_long_signal = long_signal; reset_extra_long_signal = extra_long_signal; mutex_unlock(&mts_io_mutex); } return count; } static DEVICE_ATTR_MTS(dev_attr_reset_monitor, "reset-monitor", mts_attr_show_reset_monitor, mts_attr_store_reset_monitor); static DEVICE_ATTR_RO_MTS(dev_attr_reset, "reset", mts_attr_show_gpio_pin); /* active-low socket modem reset */ static ssize_t mts_attr_store_radio_reset(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int value; int err; struct gpio_pin *pin; if (sscanf(buf, "%i", &value) != 1) { return -EINVAL; } if (value != 0) { return -EINVAL; } pin = gpio_pin_by_name("RADIO_RESET"); if (!pin) { return -ENODEV; } mutex_lock(&mts_io_mutex); // 250ms low reset err = reset_gpio_pin(pin, 250, 0); mutex_unlock(&mts_io_mutex); if (err) { return err; } return count; } static DEVICE_ATTR_MTS(dev_attr_radio_reset, "radio-reset", mts_attr_show_gpio_pin, mts_attr_store_radio_reset); /* shared gpio attributes */ static DEVICE_ATTR_MTS(dev_attr_radio_power, "radio-power", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); /* shared gpio-based LEDs */ static DEVICE_ATTR_MTS(dev_attr_led_status, "led-status", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_led_a_gpio, "led-a", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); #if LED_LS_CONTROLLABLE static DEVICE_ATTR_MTS(dev_attr_led_ls, "led-ls", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); #else static DEVICE_ATTR_RO_MTS(dev_attr_led_ls, "led-ls", mts_attr_show_gpio_pin); #endif static DEVICE_ATTR_MTS(dev_attr_led_b_gpio, "led-b", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_led_cd_gpio, "led-cd", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_led_c_gpio, "led-c", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_led_sig1_gpio, "led-sig1", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_led_sig2_gpio, "led-sig2", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_led_sig3_gpio, "led-sig3", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_led_d_gpio, "led-d", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_led_e_gpio, "led-e", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); /* eeprom info */ static ssize_t mts_attr_show_product_info(struct device *dev, struct device_attribute *attr, char *buf) { int i; ssize_t value; if (strcmp(attr->attr.name, "vendor-id") == 0) { value = sprintf(buf, "%.32s\n", id_eeprom.vendor_id); } else if (strcmp(attr->attr.name, "product-id") == 0) { value = sprintf(buf, "%.32s\n", id_eeprom.product_id); } else if (strcmp(attr->attr.name, "device-id") == 0) { value = sprintf(buf, "%.32s\n", id_eeprom.device_id); } else if (strcmp(attr->attr.name, "uuid") == 0) { //Loop Through UUID Bytes and print them in HEX for(i = 0; i < 16; i++) { value = sprintf(buf, "%02X", id_eeprom.uuid[i]); if(value == -1) { return value; } buf += value; } value = sprintf(buf, "\n"); if(value == -1) { return value; } value = 33; //16*2 (ASCII HEX) + 1 ('\n') } else if (strcmp(attr->attr.name, "hw-version") == 0) { value = sprintf(buf, "%.32s\n", id_eeprom.hw_version); } else if (strcmp(attr->attr.name, "imei") == 0) { value = sprintf(buf, "%.32s\n", id_eeprom.imei); } else if (strcmp(attr->attr.name, "mac-eth") == 0) { value = sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X\n", id_eeprom.mac_addr[0], id_eeprom.mac_addr[1], id_eeprom.mac_addr[2], id_eeprom.mac_addr[3], id_eeprom.mac_addr[4], id_eeprom.mac_addr[5]); } else { log_error("attribute '%s' not found", attr->attr.name); value = -1; } return value; } static DEVICE_ATTR_RO_MTS(dev_attr_vendor_id, "vendor-id", mts_attr_show_product_info); static DEVICE_ATTR_RO_MTS(dev_attr_product_id, "product-id", mts_attr_show_product_info); static DEVICE_ATTR_RO_MTS(dev_attr_device_id, "device-id", mts_attr_show_product_info); static DEVICE_ATTR_RO_MTS(dev_attr_uuid, "uuid", mts_attr_show_product_info); static DEVICE_ATTR_RO_MTS(dev_attr_hw_version, "hw-version", mts_attr_show_product_info); static DEVICE_ATTR_RO_MTS(dev_attr_imei, "imei", mts_attr_show_product_info); static DEVICE_ATTR_RO_MTS(dev_attr_eth_mac, "mac-eth", mts_attr_show_product_info); /* include per-device pins and attributes */ #include "mtcdt.c" static bool load_port(int port) { int port_index = port - 1; memcpy(&ap_eeprom[port_index], mts_ap_eeprom[port_index], sizeof(mts_ap_eeprom[port_index])); if (mts_ap_eeprom[port_index][0] == 0xFF) { log_error("uninitialized eeprom on accessory card %d", port); } else if (mts_ap_eeprom[port_index][0] == 0x00) { log_info("no accessory card inserted in port %d", port); } else { port_info[port_index] = kzalloc(sizeof(struct ap_info), GFP_KERNEL); if (! port_info[port_index]) { log_error("alloc of port info failed"); return false; } if (strstr(ap_eeprom[port_index].product_id, PRODUCT_ID_MTAC_GPIOB)) { if (! set_gpiob_info(port_info[port_index])) { log_error("failed to set up gpiob port info"); return false; } } else if (strstr(ap_eeprom[port_index].product_id, PRODUCT_ID_MTAC_MFSER)) { if (! set_mfser_info(port_info[port_index])) { log_error("failed to set up mfser port info"); return false; } } else if (strstr(ap_eeprom[port_index].product_id, PRODUCT_ID_MTAC_ETH)) { if (! set_eth_info(port_info[port_index])) { log_error("failed to set up eth port info"); return false; } } else if (strstr(ap_eeprom[port_index].product_id, PRODUCT_ID_MTAC_LORA)) { if (! set_lora_info(port_info[port_index])) { log_error("failed to set up lora port info"); return false; } } else { log_error("unknown accessory card [%s] in port %d", ap_eeprom[port_index].product_id, port); kfree(port_info[port_index]); port_info[port_index] = NULL; return false; } log_info("accessory card %d vendor-id: %.32s", port, ap_eeprom[port_index].vendor_id); log_info("accessory card %d product-id: %.32s", port, ap_eeprom[port_index].product_id); log_info("accessory card %d device-id: %.32s", port, ap_eeprom[port_index].device_id); log_info("accessory card %d hw-version: %.32s", port, ap_eeprom[port_index].hw_version); if (strncmp(ap_eeprom[port_index].product_id, PRODUCT_ID_MTAC_ETH, strlen(PRODUCT_ID_MTAC_ETH)) == 0) { log_info("accessory card %d mac-addr: %02X:%02X:%02X:%02X:%02X:%02X", port, ap_eeprom[port_index].mac_addr[0], ap_eeprom[port_index].mac_addr[1], ap_eeprom[port_index].mac_addr[2], ap_eeprom[port_index].mac_addr[3], ap_eeprom[port_index].mac_addr[4], ap_eeprom[port_index].mac_addr[5]); } if (strncmp(ap_eeprom[port_index].product_id, PRODUCT_ID_MTAC_LORA, strlen(PRODUCT_ID_MTAC_LORA)) == 0) { log_info("accessory card %d eui: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", port, ap_eeprom[port_index].eui[0], ap_eeprom[port_index].eui[1], ap_eeprom[port_index].eui[2], ap_eeprom[port_index].eui[3], ap_eeprom[port_index].eui[4], ap_eeprom[port_index].eui[5], ap_eeprom[port_index].eui[6], ap_eeprom[port_index].eui[7]); } if (! port_info[port_index]->setup(port)) { log_error("accessory port %d setup failed", port); port_info[port_index]->teardown(port); kfree(port_info[port_index]); port_info[port_index] = NULL; return false; } } return true; } static void init_accessory_ports(void) { int i; for (i = 1; i <= NUM_AP; i++) { if (! load_port(i)) { log_error("failed to load accessory card in port %d", i); } } } static int mts_id_eeprom_load(void) { int i; char buf[64] = {0}; char* ptr; //The mts_id_eeprom buffer is initialize once on boot //reloading the mts_io.ko module will not reinitialize this buffer //only rebooting will reinitialize this buffer memcpy(&id_eeprom, mts_id_eeprom, sizeof(mts_id_eeprom)); if (mts_id_eeprom[0] == 0xFF) { log_error("uninitialized eeprom"); return -EIO; } else { attr_group = &mtcdt_platform_attribute_group; gpio_pins = gpio_pins_mtcdt_0_0; log_info("detected board %s", HW_VERSION_MTCDT_0_0); } log_info("sizeof: %lu", (unsigned long) sizeof(struct mts_id_eeprom_layout)); log_info("vendor-id: %.32s", id_eeprom.vendor_id); log_info("product-id: %.32s", id_eeprom.product_id); log_info("device-id: %.32s", id_eeprom.device_id); log_info("hw-version: %.32s", id_eeprom.hw_version); log_info("mac-addr: %02X:%02X:%02X:%02X:%02X:%02X", id_eeprom.mac_addr[0], id_eeprom.mac_addr[1], id_eeprom.mac_addr[2], id_eeprom.mac_addr[3], id_eeprom.mac_addr[4], id_eeprom.mac_addr[5]); log_info("imei: %.32s", id_eeprom.imei); log_info("capa-gps: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_GPS) ? "yes" : "no"); log_info("capa-din: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_DIN) ? "yes" : "no"); log_info("capa-dout: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_DOUT) ? "yes" : "no"); log_info("capa-adc: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_ADC) ? "yes" : "no"); log_info("capa-wifi: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_WIFI) ? "yes" : "no"); log_info("capa-bluetooth: %s", DEVICE_CAPA(id_eeprom.capa, CAPA_BLUETOOTH) ? "yes" : "no"); 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); return 0; } static void cleanup(void) { int port; int port_index; log_info("cleaning up...."); if (mts_io_platform_device) { platform_device_unregister(mts_io_platform_device); } for (port_index = 0, port = 1; port_index < NUM_AP; port_index++, port++) { if (port_info[port_index]) { port_info[port_index]->teardown(port); kfree(port_info[port_index]); } } log_info("done cleaning up...."); } static int __init mts_io_init(void) { struct gpio_pin *pin; int ret; int port_index; 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 (NUM_AP) { for (port_index = 0; port_index < NUM_AP; port_index++) { port_info[port_index] = NULL; } init_accessory_ports(); } ret = sysfs_create_group(&mts_io_platform_device->dev.kobj, attr_group); if (ret) { cleanup(); return ret; } for (pin = gpio_pins; *pin->name; pin++) { ret = gpio_request_one(pin->pin.gpio, pin->pin.flags, pin->pin.label); if (ret) { log_debug("could not request pin %s (%d) but it could have already been requested under a different pin name", pin->name, ret); } } // start the reset handler reset_callback(NULL); return 0; } static void __exit mts_io_exit(void) { cancel_delayed_work_sync(&reset_work); cleanup(); log_info("exiting"); } module_init(mts_io_init); module_exit(mts_io_exit); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_VERSION(DRIVER_VERSION); MODULE_LICENSE("GPL"); MODULE_ALIAS("mts-io-ap1-dout"); MODULE_ALIAS("mts-io-ap1-din"); MODULE_ALIAS("mts-io-ap1-adc"); MODULE_ALIAS("mts-io-ap2-dout"); MODULE_ALIAS("mts-io-ap2-din"); MODULE_ALIAS("mts-io-ap2-adc");