Newer
Older
mbed-os / targets / TARGET_STM / TARGET_STM32L4 / TARGET_STM32L496xG / TARGET_MTS_DRAGONFLY_L496VG / ONBOARD_QUECTEL_EG25.cpp
/* mbed Microcontroller Library
 * Copyright (c) 2018 ARM Limited
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#if MBED_CONF_NSAPI_PRESENT

#include "QUECTEL_EC2X.h"
#include "ONBOARD_QUECTEL_EG25.h"
#include "gpio_api.h"
#include "platform/mbed_thread.h"
#include "PinNames.h"
#include "drivers/BufferedSerial.h"
#include "CellularLog.h"
#include "mbed_wait_api.h"

using namespace mbed;

ONBOARD_QUECTEL_EG25::ONBOARD_QUECTEL_EG25(FileHandle *fh, PinName pwr, bool active_high, PinName rst) : QUECTEL_EC2X(fh, pwr, active_high, rst)
{
    initialized = 0;
    ls = new DigitalOut(LS_LED);
    net_status = new InterruptIn(NET_STATUS);
}

nsapi_error_t ONBOARD_QUECTEL_EG25::hard_power_on()
{
    onboard_modem_init();
    return NSAPI_ERROR_OK;
}

nsapi_error_t ONBOARD_QUECTEL_EG25::hard_power_off()
{
    onboard_modem_deinit();
    return NSAPI_ERROR_OK;
}

nsapi_error_t ONBOARD_QUECTEL_EG25::soft_power_on()
{
    // See base function description. This function is for power on but reset too.
    onboard_modem_power_down();
    onboard_modem_power_up();
    return NSAPI_ERROR_OK;
}

nsapi_error_t ONBOARD_QUECTEL_EG25::soft_power_off()
{
    onboard_modem_power_down();
    return NSAPI_ERROR_OK;
}

CellularDevice *CellularDevice::get_target_default_instance()
{
    static BufferedSerial serial(MDMTXD, MDMRXD, 115200);
#if DEVICE_SERIAL_FC
    if (MDMRTS != NC && MDMCTS != NC) {
        tr_debug("Modem flow control: RTS %d CTS %d", MDMRTS, MDMCTS);
        serial.set_flow_control(SerialBase::RTSCTS, MDMRTS, MDMCTS);
    }
#endif
    static ONBOARD_QUECTEL_EG25 device(&serial, MDMPWRON, 0, MDMRST);
    return &device;
}

void ONBOARD_QUECTEL_EG25::press_power_button(int time_us)
{
    gpio_t gpio;
    gpio_init_inout(&gpio, MDMPWRON, PIN_OUTPUT, OpenDrainNoPull, 0);
    wait_us(time_us);
    gpio_init_inout(&gpio, MDMPWRON, PIN_INPUT, PullNone, 1);
}

void ONBOARD_QUECTEL_EG25::onboard_modem_init()
{
    // PWRKEY/RADIO_ONOFF = output open drain
    // RADIO_PWR = Enables the regulator. High = on. Has pulldown on board.
    // RADIO_RESET/MDMRST = Reset the radio. Open drain or float.
    // RADIO_STATUS/STATUS = input with pull up. Gets driven low when radio is on.

    gpio_t gpio;
    // Enable radio power regulator and buffer, configure RESET_N and PWR_ON.
    gpio_init_inout(&gpio, RADIO_PWR, PIN_OUTPUT, PushPullNoPull, 1);
    gpio_init_inout(&gpio, BUF_EN, PIN_OUTPUT, OpenDrainNoPull, 0);
    // MDMRST and MDMPWRON get driven low via open drain.
    gpio_init_inout(&gpio, MDMRST, PIN_INPUT, PullNone, 1);
    gpio_init_inout(&gpio, MDMPWRON, PIN_INPUT, PullNone, 1);
    gpio_init_in_ex(&gpio, RADIO_STATUS, PullUp);
    // Quectel hardware design guide recommends >= 30ms from powered until
    //  PWRKEY(MDMPWRON) is pulled low.
    wait_us(40000);
    initialized = 1;
}

void ONBOARD_QUECTEL_EG25::onboard_modem_deinit()
{
    // Make sure to power down before removing power!
    onboard_modem_power_down();
    gpio_t gpio;

    // Set all to inputs no pull. Let pull resistors do their thing. Allows
    // for lowest power draw. Disable radio power regulator and buffer.
    gpio_init_inout(&gpio, MDMRST, PIN_INPUT, PullNone, 1);
    gpio_init_inout(&gpio, MDMPWRON, PIN_INPUT, PullNone, 1);
    gpio_init_inout(&gpio, BUF_EN, PIN_INPUT, PullNone, 1);
    gpio_init_inout(&gpio, RADIO_PWR, PIN_INPUT, PullNone, 0);
    initialized = 0;
}

void ONBOARD_QUECTEL_EG25::link_status()
{
    ls->write(net_status->read());
}

void ONBOARD_QUECTEL_EG25::onboard_modem_power_up()
{
    // NET_STATUS = input. InterruptIn make LS LED follow.
    // LS_LED = output. Follow NET_STATUS.
    net_status->rise(callback(this,&ONBOARD_QUECTEL_EG25::link_status));
    net_status->fall(callback(this,&ONBOARD_QUECTEL_EG25::link_status));

    // Make sure the radio is initialized so it can be powered on.
    if (!initialized){
        onboard_modem_init();
    }

    gpio_t status;
    gpio_init_in_ex(&status, RADIO_STATUS, PullUp);

    int radio_off = gpio_read(&status);
    // If radio is on, do nothing.
    if (!radio_off) {
        return;
    }

    // Activate PWRKEY at least 500ms to turn on.
    press_power_button(550000);

    // Wait 10s for status to go low before retrying.
    uint8_t timeout = 10;
    do {
        thread_sleep_for(1000);
        radio_off = gpio_read(&status);
        if(!timeout--){
            press_power_button(550000);
            timeout = 10;
        }
    } while (radio_off);

    _at.lock();

    _at.set_at_timeout(15000);
    _at.resp_start();
    _at.set_stop_tag("RDY");
    bool rdy = _at.consume_to_stop_tag();
    _at.set_stop_tag(OK);

    _at.unlock();
    if (rdy) {
        tr_debug("Radio outputted RDY");
    } else {
        tr_debug("Radio did not output RDY within 15s.");
    }
}

void ONBOARD_QUECTEL_EG25::onboard_modem_power_down()
{
    gpio_t status;
    gpio_init_in_ex(&status, RADIO_STATUS, PullUp);
    // Need volatile so status check is not optimized out.
    volatile int radio_off = gpio_read(&status);
    // Do nothing if it's already off.
    if (radio_off) {
        return;
    }

    // Make sure the I/O are properly initialized.
    if (!initialized){
        onboard_modem_init();
    }

    do {
        // Activate PWRKEY for at least 650ms to turn off.
        press_power_button(680000);
        // Wait 40s for status to go high before resetting.
        uint8_t timeout = 40;
        do {
            thread_sleep_for(1000);
            radio_off = gpio_read(&status);
        } while (!radio_off && timeout);

        if (radio_off) {
            break;
        }
        else {
            onboard_modem_reset();
        }
    }
    while (!radio_off);
}

void ONBOARD_QUECTEL_EG25::onboard_modem_reset()
{
    gpio_t gpio;
    gpio_init_inout(&gpio, MDMRST, PIN_OUTPUT, OpenDrainNoPull, 0);
    thread_sleep_for(500);
    gpio_init_inout(&gpio, MDMRST, PIN_INPUT, PullNone, 1);
    thread_sleep_for(100);
}

#endif // MBED_CONF_NSAPI_PRESENT