#define NXP_GPIO(BANK, IO) (((( BANK - 1 ) * 32) + IO)) /* Used for both mtcap3 0.0 and 0.1 */ static struct gpio_pin gpio_pins_mtcap3_0_0[] = { { .name = "ETH_RESET", .pin = { .gpio = NXP_GPIO(1, 4), // ETH_NRST - has ext PU .flags = GPIOF_OUT_INIT_HIGH, .label = "eth-reset", }, // mts-io in init leaves eth-reset asserted even though it is really // an nrst pin, and does this across boards. This is wrong logically, // but active_low must be 0 as legacy and for this to function properly. .active_low = 0, }, { .name = "RADIO_POWER_MONITOR", .pin = { .gpio = NXP_GPIO(4, 28), // CELL_STATUS .flags = GPIOF_IN, .label = "radio-power-monitor", }, }, { .name = "RADIO_RESET", .pin = { .gpio = NXP_GPIO(5, 4), // CELL_RESET - has ext PD - inverted through FET .flags = GPIOF_OUT_INIT_LOW, .label = "radio-reset", }, }, { .name = "RADIO_ONOFF", .pin = { .gpio = NXP_GPIO(5, 8), // CELL_ONOFF - has ext PD - inverted through FET .flags = GPIOF_OUT_INIT_HIGH, .label = "radio-onoff", }, }, { .name = "RADIO_POWER", .pin = { .gpio = NXP_GPIO(5, 0), // CELL_PWR_EN - has ext PD .flags = GPIOF_OUT_INIT_HIGH, .label = "radio-power", }, }, { .name = "RADIO_NETLIGHT", // LED4 is for cell network status .pin = { .gpio = NXP_GPIO(4, 25), .flags = GPIOF_IN, .label = "radio-netlight", }, }, { .name = "DEVICE_RESET", .pin = { .gpio = NXP_GPIO(2, 13), // SWITCH_IN - has EXT PU .flags = GPIOF_IN, .label = "reset", }, .active_low = 1, }, { .name = "LORA_RESET", .pin = { .gpio = NXP_GPIO(1, 25), // LORA_RST .flags = GPIOF_OUT_INIT_LOW, .label = "lora/reset", }, .capability = CAPA_LORA, }, { .name = "LORA_LBT_RESET", .pin = { .gpio = NXP_GPIO(1, 27), // LORA_LBT_nRESET .flags = GPIOF_OUT_INIT_LOW, .label = "lora/lbt-reset", }, .capability = CAPA_LORA_LBT, .active_low = 1, }, /* LEDs */ { .name = "STATUS_LED", // DEV_LED_GN .pin = { .gpio = NXP_GPIO(2, 15), .flags = GPIOF_OUT_INIT_HIGH, .label = "led-status", }, }, { .name = "LED2", // LED2 is for LoRa status .pin = { .gpio = NXP_GPIO(3, 21), .flags = GPIOF_OUT_INIT_LOW, .label = "led-lora", }, }, { .name = "LED3", // LED3 is for Cellular status (Modem can directly enable it) .pin = { .gpio = NXP_GPIO(3, 22), .flags = GPIOF_OUT_INIT_HIGH, .label = "led-cd", }, }, { .name = "LED4", // LED4 is for power status .pin = { .gpio = NXP_GPIO(3, 23), .flags = GPIOF_OUT_INIT_LOW, .label = "led-power", }, }, // NXP_GPIO(3, 28), LED_EXTRA on the Rev 0 schem., is always on power { }, }; // Note on EG95 reset logic: this code was adapted from radio.c in // RadioSupervisoryChips/STM8L101-MTQ. // EG95 timings #define EG95_PWRKEY_KEEPOUT_WAIT_MS (60) #define EG95_PWRKEY_LOW_ON_WAIT_MS (540) // >= 500ms in datasheet #define EG95_PWRKEY_LOW_OFF_WAIT_MS (700) // >650ms in datasheet #define EG95_POWER_MON_ON_WAIT_S (30) #define EG95_POWER_MON_OFF_WAIT_S (35) #define EG95_POWER_KEEPOUT_WAIT_MS (60) #define EG95_RESET_N_WAIT_MS (300) /* radio control (power/reset) for mtcap3 */ static int radio_off_mtcap3(void) { int i = 0; int value = 0; struct gpio_pin *pwrmon_pin = gpio_pin_by_attr_name("radio-power-monitor"); struct gpio_pin *onoff_pin = gpio_pin_by_attr_name("radio-onoff"); struct gpio_pin *power_pin = gpio_pin_by_attr_name("radio-power"); if (!onoff_pin || !pwrmon_pin) { return -ENODEV; } value = gpio_get_value(pwrmon_pin->pin.gpio); if(value == 0) { log_warning("cell radio is already off"); return 0; } /* The reference manual indicates that PWRKEY is equivalent to AT+QPOWD */ log_info("turning off cell radio"); // Toggle PWRKEY - logic is reversed to the module through the FET gpio_set_value(onoff_pin->pin.gpio, 0); msleep(EG95_PWRKEY_KEEPOUT_WAIT_MS); gpio_set_value(onoff_pin->pin.gpio, 1); msleep(EG95_PWRKEY_LOW_OFF_WAIT_MS); gpio_set_value(onoff_pin->pin.gpio, 0); // Wait for module to indicate status for(i=0; i<=EG95_POWER_MON_OFF_WAIT_S; i++) { value = gpio_get_value(pwrmon_pin->pin.gpio); if(!value) { break; } msleep(1000); } // disable power to the radio; We want to do this generally for // battery powered MTCAP based devices. gpio_set_value(power_pin->pin.gpio, 0); msleep(EG95_POWER_KEEPOUT_WAIT_MS); if(value != 0) { log_warning("cell radio was still on."); } else { log_info("cell radio has been turned off"); } return 0; } static int radio_on_mtcap3(void) { int i = 0; int value = 0; struct gpio_pin *pwrmon_pin = gpio_pin_by_attr_name("radio-power-monitor"); struct gpio_pin *onoff_pin = gpio_pin_by_attr_name("radio-onoff"); struct gpio_pin *power_pin = gpio_pin_by_attr_name("radio-power"); if (!onoff_pin || !pwrmon_pin || !power_pin) { return -ENODEV; } value = gpio_get_value(pwrmon_pin->pin.gpio); if(value != 0) { log_warning("cell radio is already on"); return 0; } log_info("turning on cell radio"); // Toggle PWRKEY - lgoic is reversed to the module through the FET gpio_set_value(onoff_pin->pin.gpio, 0); msleep(EG95_PWRKEY_KEEPOUT_WAIT_MS); // Enable power to the radio gpio_set_value(power_pin->pin.gpio, 1); msleep(EG95_POWER_KEEPOUT_WAIT_MS); gpio_set_value(onoff_pin->pin.gpio, 1); msleep(EG95_PWRKEY_LOW_ON_WAIT_MS); // Wait for module to inidcate status. // The spec says >= 10s, but MTQ code does 30; will be on the order of secs for(i=0; i<=EG95_POWER_MON_ON_WAIT_S; i++) { value = gpio_get_value(pwrmon_pin->pin.gpio); if(value) { break; } msleep(1000); } // Set pwrkey high (through the FET switch); // MTQ code does this after waiting for radio-power-monitor assert gpio_set_value(onoff_pin->pin.gpio, 0); if(value == 0) { log_warning("cell radio is still off."); } else { log_info("cell radio has been turned on"); } return 0; } static ssize_t mts_attr_store_radio_power_mtcap3(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int value; int err; if (sscanf(buf, "%i", &value) != 1) { return -EINVAL; } mutex_lock(&mts_io_mutex); if (value == 0) { err = radio_off_mtcap3(); } else if (value == 1) { err = radio_on_mtcap3(); } else { err = -EINVAL; } mutex_unlock(&mts_io_mutex); if (err) { return err; } return count; } // *_radio_enable_* is here for backward compatibility static ssize_t mts_attr_store_radio_enable_mtcap3(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { // Included for legacy compatibilty. The 'radio-power' object controls // power to the module, rather than 'enable.' The radio is always // enabled, and 'radio-power' mirrors the state of the regulator enable, // alleviating confusion, as well as ensuring clean shut down which // reduce module failures. return count; } static ssize_t mts_attr_show_radio_enable_mtcap3(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", 1); } static ssize_t mts_attr_store_radio_reset_mtcap3(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int i; int value; struct gpio_pin *pwrmon_pin = gpio_pin_by_attr_name("radio-power-monitor"); struct gpio_pin *reset_pin = gpio_pin_by_attr_name("radio-reset"); if ( !pwrmon_pin || !reset_pin) { return -ENODEV; } value = gpio_get_value(pwrmon_pin->pin.gpio); if (sscanf(buf, "%i", &value) != 1) { return -EINVAL; } if (value != 0 && value != -1) { return -EINVAL; } reset_radio_udev_discovery(); mutex_lock(&mts_io_mutex); radio_off_mtcap3(); radio_on_mtcap3(); value = gpio_get_value(pwrmon_pin->pin.gpio); // Ensure that the module status indicates that it is up if(!value) { // Something has gone wrong log_warning("cell radio not responding. Applying hard reset."); // The manual advises against doing this as it is disorderly // We do it here as a last resort. gpio_set_value(reset_pin->pin.gpio, 1); msleep(EG95_RESET_N_WAIT_MS); gpio_set_value(reset_pin->pin.gpio, 0); for(i=0; i<=EG95_POWER_MON_ON_WAIT_S; i++) { value = gpio_get_value(pwrmon_pin->pin.gpio); if(value) { break; } msleep(1000); } if(value == 0) { log_warning("Unable to reset radio."); } else { log_info("cell radio has been reset"); } } else { log_info("cell radio has been reset"); } mutex_unlock(&mts_io_mutex); return count; } static ssize_t mts_attr_show_radio_power_mtcap3(struct device *dev, struct device_attribute *attr, char *buf) { int value; struct gpio_pin *pwrmon_pin = gpio_pin_by_attr_name("radio-power-monitor"); if ( !pwrmon_pin) { return -ENODEV; } value = gpio_get_value(pwrmon_pin->pin.gpio); return sprintf(buf, "%d\n", value); } static DEVICE_ATTR_MTS(dev_attr_radio_reset_mtcap3, "radio-reset", mts_attr_show_gpio_pin, mts_attr_store_radio_reset_mtcap3); static DEVICE_ATTR_MTS(dev_attr_radio_power_mtcap3, "radio-power", mts_attr_show_radio_power_mtcap3, mts_attr_store_radio_power_mtcap3); static DEVICE_ATTR_RO_MTS(dev_attr_radio_power_monitor_mtcap3, "radio-power-monitor", mts_attr_show_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_eth_reset_mtcap3, "eth-reset", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_led_lora_gpio_mtcap3, "led-lora", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); // here to maintain compatiblility static DEVICE_ATTR_MTS(dev_attr_radio_enable_mtcap3, "radio-enable", mts_attr_show_radio_enable_mtcap3, mts_attr_store_radio_enable_mtcap3); static DEVICE_ATTR_MTS(dev_attr_led_power_gpio_mtcap3, "led-power", mts_attr_show_gpio_pin, mts_attr_store_gpio_pin); static struct attribute *mtcap3_0_0_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_eth_reset_mtcap3.attr, &dev_attr_led_status.attr, &dev_attr_led_cd_gpio.attr, &dev_attr_led_lora_gpio_mtcap3.attr, &dev_attr_led_power_gpio_mtcap3.attr, /* Set to NULL if no radio -- should be 1st radio attribute */ &dev_attr_radio_reset_mtcap3.attr, &dev_attr_radio_power_mtcap3.attr, &dev_attr_radio_power_monitor_mtcap3.attr, &dev_attr_radio_reset_backoffs.attr, &dev_attr_radio_reset_backoff_index.attr, &dev_attr_radio_reset_backoff_seconds.attr, &dev_attr_radio_enable_mtcap3.attr, // UDEV notification of radio discovery &dev_attr_radio_udev_discovery.attr, &dev_attr_radio_reset_monitor.attr, NULL, }; static int is_radio_power_attr_mtcap3(struct attribute *attr) { return (attr == &dev_attr_radio_power_mtcap3.attr); } static struct attribute_group mtcap3_0_0_platform_attribute_group = { .attrs = mtcap3_0_0_platform_attributes }; // // on-board LORA attributes are to be stored in the lora/ sub-directory // // static DEVICE_ATTR_MTS(dev_attr_lora_reset_mtcap3, "reset", mts_attr_show_lora_gpio_pin, mts_attr_store_lora_gpio_pin); static DEVICE_ATTR_MTS(dev_attr_lora_lbt_reset_mtcap3, "lbt-reset", mts_attr_show_lora_gpio_pin, mts_attr_store_lora_gpio_pin); static DEVICE_ATTR_RO_MTS(dev_attr_lora_eui_mtcap3, "eui", mts_attr_show_lora_product_info); static DEVICE_ATTR_RO_MTS(dev_attr_lora_product_id_mtcap3, "product-id", mts_attr_show_lora_product_info); static DEVICE_ATTR_RO_MTS(dev_attr_lora_hw_version_mtcap3, "hw-version", mts_attr_show_lora_product_info); static struct attribute *mtcap3_0_0_lora_attributes[] = { &dev_attr_lora_eui_mtcap3.attr, &dev_attr_lora_product_id_mtcap3.attr, &dev_attr_lora_hw_version_mtcap3.attr, &dev_attr_lora_reset_mtcap3.attr, NULL, }; static struct attribute_group mtcap3_0_0_lora_attribute_group = { .attrs = mtcap3_0_0_lora_attributes }; static struct attribute *mtcap3_0_0_lora_lbt_attributes[] = { &dev_attr_lora_lbt_reset_mtcap3.attr, };