/* SPDX-License-Identifier: GPL-2.0-only */ /* * Modem power control driver. * * Ondrej Jirman * * How this works * -------------- * * The driver: * - can be registered as a platform or serial device * - will use gpios, regulator and (optionally) serial port to control the modem * - exposes a character device to control the modem power and receive various * events * - exposes sysfs interface to control modem power and wakeup * - supports multiple modem types and instances * * Power up/power down: * - may take a lot of time (eg. ~13-22s powerup, >22s powerdown) * - happens on a private workqueue under a lock * - may happen from shutdown hook * - prevents suspend when powerup/powerdown is in progress * - is serialized and there's no abort of in-progress operations * - for specific power sequence see comments in the section for each * supported modem variant * - the driver monitors the power status of the modem (optionally) * and tries to complete the powerdown initiated via AT command * - the driver tries to detect when the modem is killswitched off * and updates the driver status to reflect that * * Suspend/resume: * - suspend is blocked if powerup/down is in progress * - modem can wakeup the host over gpio based IRQ (RI signal) * - the driver will assert ap_ready after resume finishes * * Rfkill: * - the driver implements a rfkill interface if rfkill gpio is available */ //#define DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DRIVER_NAME "modem-power" enum { MPWR_REQ_NONE = 0, MPWR_REQ_RESET, MPWR_REQ_PWDN, MPWR_REQ_PWUP, }; enum { MPWR_MODE_NORMAL = 1, MPWR_MODE_DUMB, MPWR_MODE_FASTBOOT, MPWR_MODE_ALT1, MPWR_MODE_ALT2, }; struct mpwr_dev; struct mpwr_gpio { const char* name; unsigned desc_off; int flags; bool required; int irq_flags; unsigned irq_off; }; #define MPWR_GPIO_DEF(_name, _flags, _req) \ { .name = #_name, \ .desc_off = offsetof(struct mpwr_dev, _name##_gpio), \ .flags = _flags, \ .required = _req, \ } #define MPWR_GPIO_DEF_IRQ(_name, _flags, _req, _irq_flags) \ { .name = #_name, \ .desc_off = offsetof(struct mpwr_dev, _name##_gpio), \ .flags = _flags, \ .required = _req, \ .irq_flags = _irq_flags, \ .irq_off = offsetof(struct mpwr_dev, _name##_irq), \ } struct mpwr_variant { int (*power_init)(struct mpwr_dev* mpwr); int (*power_up)(struct mpwr_dev* mpwr); int (*power_down)(struct mpwr_dev* mpwr); int (*reset)(struct mpwr_dev* mpwr); void (*recv_msg)(struct mpwr_dev *mpwr, const char *msg); int (*suspend)(struct mpwr_dev *mpwr); int (*resume)(struct mpwr_dev *mpwr); const struct mpwr_gpio* gpios; bool regulator_required; bool monitor_wakeup; }; struct mpwr_dev { struct device *dev; const struct mpwr_variant* variant; wait_queue_head_t wait; /* serdev */ struct serdev_device *serdev; char rcvbuf[4096]; size_t rcvbuf_fill; char msg[4096]; int msg_len; int msg_ok; //struct kfifo kfifo; DECLARE_KFIFO(kfifo, unsigned char, 4096); /* power */ struct regulator *regulator; struct regulator *regulator_vbus; /* outputs */ struct gpio_desc *enable_gpio; struct gpio_desc *reset_gpio; struct gpio_desc *pwrkey_gpio; struct gpio_desc *sleep_gpio; struct gpio_desc *dtr_gpio; struct gpio_desc *host_ready_gpio; struct gpio_desc *cts_gpio; struct gpio_desc *rts_gpio; /* inputs */ struct gpio_desc *status_gpio; struct gpio_desc *wakeup_gpio; int wakeup_irq; bool status_pwrkey_multiplexed; /* config */ struct cdev cdev; dev_t major; /* rfkill */ struct rfkill *rfkill; /* powerup/dn work queue */ struct workqueue_struct *wq; struct work_struct power_work; struct work_struct finish_pdn_work; struct mutex modem_lock; // change spinlock_t lock; /* protects last_request */ int last_request; int powerup_mode; ktime_t last_wakeup; struct timer_list wd_timer; struct delayed_work host_ready_work; unsigned long flags[1]; }; enum { /* modem is powered */ MPWR_F_POWERED, MPWR_F_POWER_CHANGE_INPROGRESS, MPWR_F_KILLSWITCHED, /* we got a wakeup from the modem */ MPWR_F_GOT_WAKEUP, /* serdev */ MPWR_F_RECEIVING_MSG, /* eg25 */ MPWR_F_GOT_PDN, /* config options */ MPWR_F_BLOCKED, /* file */ MPWR_F_OPEN, MPWR_F_OVERFLOW, }; static struct class* mpwr_class; static int mpwr_serdev_at_cmd(struct mpwr_dev *mpwr, const char *msg, int timeout_ms); static int mpwr_serdev_at_cmd_with_retry(struct mpwr_dev *mpwr, const char *msg, int timeout_ms, int tries); static int mpwr_serdev_at_cmd_with_retry_ignore_timeout(struct mpwr_dev *mpwr, const char *msg, int timeout_ms, int tries); // {{{ mg2723 variant static int mpwr_mg2723_power_init(struct mpwr_dev* mpwr) { // if the device has power applied or doesn't have regulator // configured (we assume it's always powered) initialize GPIO // to shut it down initially if (!mpwr->regulator || regulator_is_enabled(mpwr->regulator)) { gpiod_set_value(mpwr->enable_gpio, 0); gpiod_set_value(mpwr->reset_gpio, 1); } else { // device is not powered, don't drive the gpios gpiod_direction_input(mpwr->enable_gpio); gpiod_direction_input(mpwr->reset_gpio); } return 0; } static int mpwr_mg2723_power_up(struct mpwr_dev* mpwr) { int ret; // power up if (mpwr->regulator) { ret = regulator_enable(mpwr->regulator); if (ret < 0) { dev_err(mpwr->dev, "can't enable power supply err=%d", ret); return ret; } } gpiod_direction_output(mpwr->enable_gpio, 1); gpiod_direction_output(mpwr->reset_gpio, 1); msleep(300); gpiod_set_value(mpwr->reset_gpio, 0); return 0; } static int mpwr_mg2723_power_down(struct mpwr_dev* mpwr) { gpiod_set_value(mpwr->enable_gpio, 0); msleep(50); if (mpwr->regulator) { regulator_disable(mpwr->regulator); gpiod_direction_input(mpwr->enable_gpio); gpiod_direction_input(mpwr->reset_gpio); } else { gpiod_set_value(mpwr->reset_gpio, 1); } return 0; } static int mpwr_mg2723_reset(struct mpwr_dev* mpwr) { gpiod_set_value(mpwr->reset_gpio, 1); msleep(300); gpiod_set_value(mpwr->reset_gpio, 0); return 0; } static const struct mpwr_gpio mpwr_mg2723_gpios[] = { MPWR_GPIO_DEF(enable, GPIOD_IN, true), MPWR_GPIO_DEF(reset, GPIOD_IN, true), MPWR_GPIO_DEF_IRQ(wakeup, GPIOD_IN, true, IRQF_TRIGGER_FALLING), { }, }; static const struct mpwr_variant mpwr_mg2723_variant = { .power_init = mpwr_mg2723_power_init, .power_up = mpwr_mg2723_power_up, .power_down = mpwr_mg2723_power_down, .reset = mpwr_mg2723_reset, .gpios = mpwr_mg2723_gpios, }; // }}} // {{{ eg25 variant static bool mpwr_eg25_qcfg_airplanecontrol_is_ok(const char* v) { return strstarts(v, "1,"); } struct mpwr_eg25_qcfg { const char* name; const char* val; bool (*is_ok)(const char* val); }; #define EG25G_LATEST_KNOWN_FIRMWARE "EG25GGBR07A08M2G_01.002.07" static const struct mpwr_eg25_qcfg mpwr_eg25_qcfgs[] = { //{ "risignaltype", "\"respective\"", }, { "risignaltype", "\"physical\"", }, { "urc/ri/ring", "\"pulse\",1,1000,5000,\"off\",1", }, { "urc/ri/smsincoming", "\"pulse\",1,1", }, { "urc/ri/other", "\"off\",1,1", }, { "urc/ri/pin", "uart_ri", }, { "urc/delay", "0", }, //{ "sleep/datactrl", "0,300,1", }, { "sleepind/level", "0", }, { "wakeupin/level", "0", }, { "ApRstLevel", "0", }, { "ModemRstLevel", "0", }, // in EG25-G this tries to modify file in /etc (read-only) // and fails //{ "dbgctl", "0", }, // we don't need AP_READY { "apready", "0,0,500", }, { "airplanecontrol", "1", mpwr_eg25_qcfg_airplanecontrol_is_ok }, // available since firmware R07A08_01.002.01.002 { "fast/poweroff", "1" }, }; static char* mpwr_serdev_get_response_value(struct mpwr_dev *mpwr, const char* prefix) { int off; for (off = 0; off < mpwr->msg_len; off += strlen(mpwr->msg + off) + 1) if (strstarts(mpwr->msg + off, prefix)) return mpwr->msg + off + strlen(prefix); return NULL; } static struct gpio_desc *mpwr_eg25_get_pwrkey_gpio(struct mpwr_dev *mpwr) { if (mpwr->status_pwrkey_multiplexed) return mpwr->status_gpio; return mpwr->pwrkey_gpio; } /* * Gpio meanings * ------------- * * enable_gpio - 1 = enables RF, 0 = disables RF * sleep_gpio - 1 = puts modem to sleep, 0 = wakes up the modem (must be 0 * during poweron) * reset_gpio - accepts 150-460ms reset pulse (high __|^|__) * pwrkey_gpio - accepts 100ms-650ms pulse for powerup (high __|^|__) * 650ms+ pulse for powerdown * (initiated after pulse ends, pulse may have indefinite * duration) * status_gpio - modem power status 0 = powered 1 = unpowered * wakeup_gpio - "ring indicator" output from the modem * host_ready_gpio - AP_READY pin - host is ready to receive URCs * * (pwrkey may be multiplexed with status_gpio) * * Modem behavior * -------------- * * wakeup_gpio (RI): * - goes high shortly after power is applied (~15ms) * - goes low when RDY is sent * * dtr_gpio * - when high, modem can sleep if requested * - H->L will wake up a sleeping modem * - internal pull-up * * ri * - pulled low when there's URC * - modem wakes up on URC automatically * * - AT+QURCCFG * - AT+QINDCFG="csq",1 * - AT+QINDCFG="ring",1 * - AT+QINDCFG="smsincoming",1 * - AT+CGREG=0 * - AT+CREG=0 * * - AT+QURCCFG="urcport","uart1" */ static int mpwr_eg25_power_up(struct mpwr_dev* mpwr) { struct gpio_desc *pwrkey_gpio = mpwr_eg25_get_pwrkey_gpio(mpwr); bool wakeup_ok, status_ok; bool needs_restart = false; u32 speed = 115200; int ret, i, off; ktime_t start; int mode = mpwr->powerup_mode; if (regulator_is_enabled(mpwr->regulator)) dev_warn(mpwr->dev, "regulator was already enabled during powerup"); /* Enable the modem power. */ ret = regulator_enable(mpwr->regulator); if (ret < 0) { dev_err(mpwr->dev, "can't enable power supply err=%d", ret); return ret; } ret = regulator_enable(mpwr->regulator_vbus); if (ret < 0) { dev_err(mpwr->dev, "can't enable vbus power supply err=%d", ret); regulator_disable(mpwr->regulator); return ret; } /* Drive default gpio signals during powerup */ /* host_ready_gpio should be 1 during normal powerup */ gpiod_direction_output(mpwr->host_ready_gpio, mode != MPWR_MODE_ALT2); /* #W_DISABLE must be left pulled up during modem power up * early on, because opensource bootloader uses this signal to enter * fastboot mode when it's pulled down. * * This should be 1 for normal powerup and 0 for fastboot mode with * special Biktor's firmware. */ gpiod_direction_output(mpwr->enable_gpio, mode != MPWR_MODE_FASTBOOT); gpiod_direction_output(mpwr->sleep_gpio, 0); gpiod_direction_output(mpwr->reset_gpio, 0); gpiod_direction_output(pwrkey_gpio, 0); /* dtr_gpio should be 0 during normal powerup */ gpiod_direction_output(mpwr->dtr_gpio, mode == MPWR_MODE_ALT1); /* Wait for powerup. (30ms min. according to datasheet) */ msleep(50); /* Send 200ms pwrkey pulse to initiate poweron */ gpiod_set_value(pwrkey_gpio, 1); msleep(200); gpiod_set_value(pwrkey_gpio, 0); /* skip modem killswitch status checks in fastboot bootloader entry mode */ if (mode != MPWR_MODE_NORMAL) goto open_serdev; /* Switch status key to input, in case it's multiplexed with pwrkey. */ gpiod_direction_input(mpwr->status_gpio); /* * Wait for status/wakeup change, assume good values, if CTS/status * signals, are not configured. */ status_ok = mpwr->status_gpio ? false : true; wakeup_ok = mpwr->wakeup_gpio ? false : true; /* wait up to 10s for status */ start = ktime_get(); while (ktime_ms_delta(ktime_get(), start) < 10000) { if (!wakeup_ok && mpwr->wakeup_gpio && gpiod_get_value(mpwr->wakeup_gpio)) { dev_info(mpwr->dev, "wakeup ok\n"); wakeup_ok = true; } if (!status_ok && mpwr->status_gpio && !gpiod_get_value(mpwr->status_gpio)) { dev_info(mpwr->dev, "status ok\n"); status_ok = true; } /* modem is ready */ if (wakeup_ok && status_ok) break; msleep(50); } if (!wakeup_ok) { dev_err(mpwr->dev, "The modem looks kill-switched\n"); if (!test_and_set_bit(MPWR_F_KILLSWITCHED, mpwr->flags)) sysfs_notify(&mpwr->dev->kobj, NULL, "killswitched"); goto err_shutdown_noclose; } if (!status_ok) { dev_err(mpwr->dev, "The modem didn't report powerup success in time\n"); goto err_shutdown_noclose; } if (test_and_clear_bit(MPWR_F_KILLSWITCHED, mpwr->flags)) sysfs_notify(&mpwr->dev->kobj, NULL, "killswitched"); open_serdev: /* open serial console */ ret = serdev_device_open(mpwr->serdev); if (ret) { dev_err(mpwr->dev, "error opening serdev (%d)\n", ret); goto err_shutdown_noclose; } of_property_read_u32(mpwr->dev->of_node, "current-speed", &speed); serdev_device_set_baudrate(mpwr->serdev, speed); serdev_device_set_flow_control(mpwr->serdev, false); ret = serdev_device_set_parity(mpwr->serdev, SERDEV_PARITY_NONE); if (ret) { dev_err(mpwr->dev, "error setting serdev parity (%d)\n", ret); goto err_shutdown; } if (mode != MPWR_MODE_NORMAL) goto powered_up; ret = mpwr_serdev_at_cmd_with_retry_ignore_timeout(mpwr, "AT&FE0", 1000, 30); if (ret) goto err_shutdown; /* print firmware version */ ret = mpwr_serdev_at_cmd_with_retry(mpwr, "AT+QVERSION;+QSUBSYSVER", 1000, 15); if (ret == 0 && mpwr->msg_len > 0) { bool outdated = false; dev_info(mpwr->dev, "===================================================\n"); for (off = 0; off < mpwr->msg_len; off += strlen(mpwr->msg + off) + 1) { if (strstr(mpwr->msg + off, "Project Rev") && !strstr(mpwr->msg + off, EG25G_LATEST_KNOWN_FIRMWARE)) outdated = true; dev_info(mpwr->dev, "%s\n", mpwr->msg + off); } dev_info(mpwr->dev, "===================================================\n"); if (outdated) dev_warn(mpwr->dev, "Your modem has an outdated firmware. Latest know version is %s. Consider updating.\n", EG25G_LATEST_KNOWN_FIRMWARE); } /* print ADB key to dmesg */ ret = mpwr_serdev_at_cmd_with_retry(mpwr, "AT+QADBKEY?", 1000, 15); if (ret == 0) { const char *val = mpwr_serdev_get_response_value(mpwr, "+QADBKEY: "); if (val) dev_info(mpwr->dev, "ADB KEY is '%s' (you can use it to unlock ADB access to the modem, see https://xnux.eu/devices/feature/modem-pp.html)\n", val); } // check DAI config ret = mpwr_serdev_at_cmd_with_retry(mpwr, "AT+QDAI?", 1000, 15); if (ret == 0) { const char *val = mpwr_serdev_get_response_value(mpwr, "+QDAI: "); const char *needed_val = NULL; char buf[128]; if (val) { of_property_read_string(mpwr->dev->of_node, "quectel,qdai", &needed_val); if (needed_val && strcmp(needed_val, val)) { dev_warn(mpwr->dev, "QDAI is '%s' (changing to '%s')\n", val, needed_val); /* update qdai */ snprintf(buf, sizeof buf, "AT+QDAI=%s", needed_val); ret = mpwr_serdev_at_cmd(mpwr, buf, 5000); if (ret == 0) needs_restart = true; } else { dev_info(mpwr->dev, "QDAI is '%s'\n", val); } } } /* reset the modem, to apply QDAI config if necessary */ if (needs_restart) { dev_info(mpwr->dev, "Restarting modem\n"); /* reboot is broken with fastboot enabled */ mpwr_serdev_at_cmd(mpwr, "AT+QCFG=\"fast/poweroff\",0", 5000); ret = mpwr_serdev_at_cmd(mpwr, "AT+CFUN=1,1", 5000); if (ret) goto err_shutdown; /* wait a bit before starting to probe the modem again */ msleep(6000); ret = mpwr_serdev_at_cmd_with_retry_ignore_timeout(mpwr, "AT&FE0", 1000, 30); if (ret) goto err_shutdown; // wait until QDAI starts succeeding (then the modem is ready // to accept the following QCFGs) ret = mpwr_serdev_at_cmd_with_retry(mpwr, "AT+QDAI?", 1000, 15); if (ret) goto err_shutdown; } /* check and update important QCFGs */ for (i = 0; i < ARRAY_SIZE(mpwr_eg25_qcfgs); i++) { const char* name = mpwr_eg25_qcfgs[i].name; const char* needed_val = mpwr_eg25_qcfgs[i].val; bool (*is_ok)(const char* val) = mpwr_eg25_qcfgs[i].is_ok; const char *val; char buf[128]; snprintf(buf, sizeof buf, "AT+QCFG=\"%s\"", name); ret = mpwr_serdev_at_cmd(mpwr, buf, 1000); if (ret) continue; snprintf(buf, sizeof buf, "+QCFG: \"%s\",", name); val = mpwr_serdev_get_response_value(mpwr, buf); if (val) { if (needed_val && (is_ok ? !is_ok(val) : strcmp(needed_val, val))) { dev_info(mpwr->dev, "QCFG '%s' is '%s' (changing to '%s')\n", name, val, needed_val); /* update qcfg */ snprintf(buf, sizeof buf, "AT+QCFG=\"%s\",%s", name, needed_val); ret = mpwr_serdev_at_cmd(mpwr, buf, 1000); if (ret) break; /* go to next QCFG */ } else { dev_info(mpwr->dev, "QCFG '%s' is '%s'\n", name, val); } } } /* setup URC port */ ret = mpwr_serdev_at_cmd(mpwr, "AT+QURCCFG=\"urcport\",\"all\"", 2000); if (ret) { dev_info(mpwr->dev, "Your modem doesn't support AT+QURCCFG=\"urcport\",\"all\", consider upgrading the firmware.\n"); ret = mpwr_serdev_at_cmd(mpwr, "AT+QURCCFG=\"urcport\",\"usbat\"", 2000); if (ret) dev_err(mpwr->dev, "Modem may not report URCs to the right port!\n"); } /* enable the modem to go to sleep when DTR is low */ ret = mpwr_serdev_at_cmd(mpwr, "AT+QSCLK=1", 2000); if (ret) dev_err(mpwr->dev, "Modem will probably not sleep!\n"); powered_up: // if we're signaling some alternate boot mode via GPIO, we need to // sleep here so that modem's boot script notices the gpio if (mode == MPWR_MODE_ALT1 || mode == MPWR_MODE_FASTBOOT || mode == MPWR_MODE_ALT2) msleep(12000); gpiod_direction_output(mpwr->dtr_gpio, 1); return 0; err_shutdown: serdev_device_close(mpwr->serdev); err_shutdown_noclose: dev_warn(mpwr->dev, "Forcibly cutting off power, data loss may occur.\n"); gpiod_direction_input(mpwr->enable_gpio); gpiod_direction_input(mpwr->reset_gpio); gpiod_direction_input(mpwr->sleep_gpio); gpiod_direction_input(pwrkey_gpio); gpiod_direction_input(mpwr->host_ready_gpio); gpiod_direction_input(mpwr->dtr_gpio); regulator_disable(mpwr->regulator_vbus); regulator_disable(mpwr->regulator); return -ENODEV; } static int mpwr_eg25_power_down_finish(struct mpwr_dev* mpwr) { struct gpio_desc *pwrkey_gpio = mpwr_eg25_get_pwrkey_gpio(mpwr); ktime_t start = ktime_get(); int ret; serdev_device_close(mpwr->serdev); /* * This function is called right after POWERED DOWN message is received. * * In case of fast/poweroff == 1, no POWERED DOWN message is sent. * Fast power off times are around 1s since the end of 800ms * POK pulse. * * When the modem powers down RI (wakeup) goes low and STATUS goes * high at the same time. Status is not connected on some boards. * RI should be inactive during poweroff, but we don't know for sure. * * Therfore: * - wait for STATUS going low * - in case that's not available wait for RI going low * - in case timings seem off, warn the user * * In addition, some boards have PWRKEY multiplexed with STATUS signal. * In that case we need to switch STATUS to output high level, as soon * as it goes low in order to prevent a power-up signal being registered * by the modem. */ if (mpwr->status_gpio) { /* wait up to 30s for status going high */ while (ktime_ms_delta(ktime_get(), start) < 30000) { if (gpiod_get_value(mpwr->status_gpio)) { if (ktime_ms_delta(ktime_get(), start) < 500) dev_warn(mpwr->dev, "STATUS signal is high too soon during powerdown. Modem is already off?\n"); goto powerdown; } msleep(20); } dev_warn(mpwr->dev, "STATUS signal didn't go high during shutdown. Modem is still on?\n"); goto force_powerdown; } else { clear_bit(MPWR_F_GOT_WAKEUP, mpwr->flags); if (!gpiod_get_value(mpwr->wakeup_gpio)) { dev_warn(mpwr->dev, "RI signal is low too soon during powerdown. Modem is already off, or spurious wakeup?\n"); msleep(2000); goto powerdown; } ret = wait_event_timeout(mpwr->wait, test_bit(MPWR_F_GOT_WAKEUP, mpwr->flags), msecs_to_jiffies(30000)); if (ret <= 0) { dev_warn(mpwr->dev, "RI signal didn't go low during shutdown, is modem really powering down?\n"); goto force_powerdown; } if (ktime_ms_delta(ktime_get(), start) < 500) { dev_warn(mpwr->dev, "RI signal is low too soon during powerdown. Modem is already off, or spurious wakeup?\n"); msleep(2000); goto powerdown; } } powerdown: gpiod_direction_input(mpwr->enable_gpio); gpiod_direction_input(mpwr->reset_gpio); gpiod_direction_input(mpwr->sleep_gpio); gpiod_direction_input(pwrkey_gpio); gpiod_direction_input(mpwr->host_ready_gpio); gpiod_direction_input(mpwr->dtr_gpio); regulator_disable(mpwr->regulator_vbus); regulator_disable(mpwr->regulator); return 0; force_powerdown: dev_warn(mpwr->dev, "Forcibly cutting off power, data loss may occur.\n"); goto powerdown; } static int mpwr_eg25_power_down(struct mpwr_dev* mpwr) { struct gpio_desc *pwrkey_gpio = mpwr_eg25_get_pwrkey_gpio(mpwr); //int ret; /* Send 800ms pwrkey pulse to initiate powerdown. */ gpiod_direction_output(pwrkey_gpio, 1); msleep(800); gpiod_set_value(pwrkey_gpio, 0); /* Switch status key to input, in case it's multiplexed with pwrkey. */ gpiod_direction_input(mpwr->status_gpio); msleep(20); #if 0 // wait for POWERED DOWN message clear_bit(MPWR_F_GOT_PDN, mpwr->flags); ret = wait_event_timeout(mpwr->wait, test_bit(MPWR_F_GOT_PDN, mpwr->flags), msecs_to_jiffies(7000)); if (ret <= 0) dev_warn(mpwr->dev, "POWERED DOWN message not received, is modem really powering down?\n"); #endif return mpwr_eg25_power_down_finish(mpwr); } static void mpwr_finish_pdn_work(struct work_struct *work) { /* struct mpwr_dev *mpwr = container_of(work, struct mpwr_dev, power_work); unsigned long flags; spin_lock_irqsave(&mpwr->lock, flags); spin_unlock_irqrestore(&mpwr->lock, flags); pm_stay_awake(mpwr->dev); mutex_lock(&mpwr->modem_lock); mutex_unlock(&mpwr->modem_lock); pm_relax(mpwr->dev); */ } static void mpwr_eg25_receive_msg(struct mpwr_dev *mpwr, const char *msg) { unsigned int msg_len; if (!strcmp(msg, "POWERED DOWN")) { // system is powering down set_bit(MPWR_F_GOT_PDN, mpwr->flags); wake_up(&mpwr->wait); /* if (mutex_trylock(&mpwr->modem_lock)) { // if no power op is in progress, this means userspace // tried to shut the modem down via AT command, finish up // the job pm_stay_awake(mpwr->dev); queue_work(mpwr->wq, &mpwr->power_work); dev_warn(mpwr->dev, "userspace shut down the modem via AT command, finishing the job\n"); mpwr_eg25_power_down_finish(mpwr); mutex_unlock(&mpwr->modem_lock); pm_relax(mpwr->dev); } */ return; } if (!strcmp(msg, "RDY")) { // system is ready after powerup return; } if (!test_bit(MPWR_F_OPEN, mpwr->flags)) return; msg_len = strlen(msg); if (msg_len + 1 > kfifo_avail(&mpwr->kfifo)) { if (!test_and_set_bit(MPWR_F_OVERFLOW, mpwr->flags)) wake_up(&mpwr->wait); return; } kfifo_in(&mpwr->kfifo, msg, msg_len); kfifo_in(&mpwr->kfifo, "\n", 1); wake_up(&mpwr->wait); } static void mpwr_host_ready_work(struct work_struct *work) { struct mpwr_dev *mpwr = container_of(work, struct mpwr_dev, host_ready_work.work); int ret; mutex_lock(&mpwr->modem_lock); gpiod_direction_output(mpwr->dtr_gpio, 0); /* * We need to give the modem some time to wake up. */ msleep(5); ret = mpwr_serdev_at_cmd(mpwr, "AT+QCFG=\"urc/cache\",0", 500); if (ret) dev_warn(mpwr->dev, "Failed to disable urc/cache, you may not be able to see URCs\n"); gpiod_direction_output(mpwr->dtr_gpio, 1); mutex_unlock(&mpwr->modem_lock); gpiod_direction_output(mpwr->host_ready_gpio, 1); } static int mpwr_eg25_suspend(struct mpwr_dev *mpwr) { int ret; cancel_delayed_work_sync(&mpwr->host_ready_work); gpiod_direction_output(mpwr->host_ready_gpio, 0); mutex_lock(&mpwr->modem_lock); gpiod_direction_output(mpwr->dtr_gpio, 0); msleep(5); ret = mpwr_serdev_at_cmd(mpwr, "AT+QCFG=\"urc/cache\",1", 500); if (ret) dev_warn(mpwr->dev, "Failed to enable urc/cache, you may lose URCs during suspend\n"); gpiod_direction_output(mpwr->dtr_gpio, 1); mutex_unlock(&mpwr->modem_lock); return 0; } static int mpwr_eg25_resume(struct mpwr_dev *mpwr) { //gpiod_direction_output(mpwr->dtr_gpio, 0); // delay disabling URC cache until the whole system is hopefully resumed... schedule_delayed_work(&mpwr->host_ready_work, msecs_to_jiffies(1000)); return 0; } static const struct mpwr_gpio mpwr_eg25_gpios[] = { MPWR_GPIO_DEF(enable, GPIOD_OUT_HIGH, true), MPWR_GPIO_DEF(reset, GPIOD_OUT_LOW, true), MPWR_GPIO_DEF(pwrkey, GPIOD_OUT_LOW, false), MPWR_GPIO_DEF(dtr, GPIOD_OUT_LOW, true), MPWR_GPIO_DEF(status, GPIOD_IN, false), MPWR_GPIO_DEF_IRQ(wakeup, GPIOD_IN, true, IRQF_TRIGGER_FALLING), // XXX: not really needed... MPWR_GPIO_DEF(sleep, GPIOD_OUT_LOW, false), MPWR_GPIO_DEF(host_ready, GPIOD_OUT_HIGH, false), MPWR_GPIO_DEF(cts, GPIOD_IN, false), MPWR_GPIO_DEF(rts, GPIOD_OUT_LOW, false), { }, }; static const struct mpwr_variant mpwr_eg25_variant = { .power_up = mpwr_eg25_power_up, .power_down = mpwr_eg25_power_down, .recv_msg = mpwr_eg25_receive_msg, .suspend = mpwr_eg25_suspend, .resume = mpwr_eg25_resume, .gpios = mpwr_eg25_gpios, .regulator_required = true, .monitor_wakeup = true, }; // }}} // {{{ generic helpers static void mpwr_reset(struct mpwr_dev* mpwr) { struct device *dev = mpwr->dev; int ret; if (!test_bit(MPWR_F_POWERED, mpwr->flags)) { dev_err(dev, "reset requested but device is not enabled"); return; } if (!mpwr->reset_gpio) { dev_err(dev, "reset is not configured for this device"); return; } if (!mpwr->variant->reset) { dev_err(dev, "reset requested but not implemented"); return; } dev_info(dev, "resetting"); ret = mpwr->variant->reset(mpwr); if (ret) { dev_err(dev, "reset failed"); } } static void mpwr_power_down(struct mpwr_dev* mpwr) { struct device *dev = mpwr->dev; ktime_t start = ktime_get(); int ret; if (!test_bit(MPWR_F_POWERED, mpwr->flags)) return; if (!mpwr->variant->power_down) { dev_err(dev, "power down requested but not implemented"); return; } dev_info(dev, "powering down"); ret = mpwr->variant->power_down(mpwr); if (ret) { dev_err(dev, "power down failed"); } else { clear_bit(MPWR_F_POWERED, mpwr->flags); sysfs_notify(&mpwr->dev->kobj, NULL, "powered"); dev_info(mpwr->dev, "powered down in %lld ms\n", ktime_ms_delta(ktime_get(), start)); } } static void mpwr_power_up(struct mpwr_dev* mpwr) { struct device *dev = mpwr->dev; ktime_t start = ktime_get(); int ret; if (test_bit(MPWR_F_POWERED, mpwr->flags)) return; if (!mpwr->variant->power_up) { dev_err(dev, "power up requested but not implemented"); return; } dev_info(dev, "powering up"); ret = mpwr->variant->power_up(mpwr); if (ret) { dev_err(dev, "power up failed"); } else { set_bit(MPWR_F_POWERED, mpwr->flags); sysfs_notify(&mpwr->dev->kobj, NULL, "powered"); dev_info(mpwr->dev, "powered up in %lld ms\n", ktime_ms_delta(ktime_get(), start)); } } // }}} // {{{ chardev static int mpwr_release(struct inode *ip, struct file *fp) { struct mpwr_dev* mpwr = fp->private_data; clear_bit(MPWR_F_OPEN, mpwr->flags); return 0; } static int mpwr_open(struct inode *ip, struct file *fp) { struct mpwr_dev* mpwr = container_of(ip->i_cdev, struct mpwr_dev, cdev); fp->private_data = mpwr; if (test_and_set_bit(MPWR_F_OPEN, mpwr->flags)) return -EBUSY; nonseekable_open(ip, fp); return 0; } static ssize_t mpwr_read(struct file *fp, char __user *buf, size_t len, loff_t *off) { struct mpwr_dev* mpwr = fp->private_data; int non_blocking = fp->f_flags & O_NONBLOCK; unsigned int copied; int ret; if (non_blocking && kfifo_is_empty(&mpwr->kfifo)) return -EWOULDBLOCK; ret = wait_event_interruptible(mpwr->wait, !kfifo_is_empty(&mpwr->kfifo) || test_bit(MPWR_F_OVERFLOW, mpwr->flags)); if (ret) return ret; if (test_and_clear_bit(MPWR_F_OVERFLOW, mpwr->flags)) { if (len < 9) return -E2BIG; if (copy_to_user(buf, "OVERFLOW\n", 9)) return -EFAULT; return 9; } ret = kfifo_to_user(&mpwr->kfifo, buf, len, &copied); return ret ? ret : copied; } static unsigned int mpwr_poll(struct file *fp, poll_table *wait) { struct mpwr_dev* mpwr = fp->private_data; poll_wait(fp, &mpwr->wait, wait); if (!kfifo_is_empty(&mpwr->kfifo)) return EPOLLIN | EPOLLRDNORM; return 0; } static const struct file_operations mpwr_fops = { .owner = THIS_MODULE, .open = mpwr_open, .release = mpwr_release, .llseek = noop_llseek, .read = mpwr_read, .poll = mpwr_poll, }; // }}} static void mpwr_work_handler(struct work_struct *work) { struct mpwr_dev *mpwr = container_of(work, struct mpwr_dev, power_work); unsigned long flags; int last_request; spin_lock_irqsave(&mpwr->lock, flags); last_request = mpwr->last_request; mpwr->last_request = 0; spin_unlock_irqrestore(&mpwr->lock, flags); pm_stay_awake(mpwr->dev); mutex_lock(&mpwr->modem_lock); if (last_request == MPWR_REQ_RESET) { mpwr_reset(mpwr); } else if (last_request == MPWR_REQ_PWDN) { mpwr_power_down(mpwr); } else if (last_request == MPWR_REQ_PWUP) { mpwr_power_up(mpwr); } mutex_unlock(&mpwr->modem_lock); clear_bit(MPWR_F_POWER_CHANGE_INPROGRESS, mpwr->flags); sysfs_notify(&mpwr->dev->kobj, NULL, "is_busy"); wake_up(&mpwr->wait); pm_relax(mpwr->dev); } static void mpwr_request_power_change(struct mpwr_dev* mpwr, int request, int mode) { unsigned long flags; set_bit(MPWR_F_POWER_CHANGE_INPROGRESS, mpwr->flags); sysfs_notify(&mpwr->dev->kobj, NULL, "is_busy"); spin_lock_irqsave(&mpwr->lock, flags); mpwr->last_request = request; if (mode >= 0) mpwr->powerup_mode = mode; spin_unlock_irqrestore(&mpwr->lock, flags); queue_work(mpwr->wq, &mpwr->power_work); } static irqreturn_t mpwr_gpio_isr(int irq, void *dev_id) { struct mpwr_dev *mpwr = dev_id; if (irq == mpwr->wakeup_irq) { dev_dbg(mpwr->dev, "wakeup irq\n"); if (device_can_wakeup(mpwr->dev)) pm_wakeup_event(mpwr->dev, 2000); set_bit(MPWR_F_GOT_WAKEUP, mpwr->flags); spin_lock(&mpwr->lock); mpwr->last_wakeup = ktime_get(); spin_unlock(&mpwr->lock); wake_up(&mpwr->wait); } return IRQ_HANDLED; } static void mpwr_wd_timer_fn(struct timer_list *t) { struct mpwr_dev *mpwr = from_timer(mpwr, t, wd_timer); if (!mpwr->variant->monitor_wakeup || !test_bit(MPWR_F_POWERED, mpwr->flags)) return; /* * Monitor wakeup status: * * If RI signal is low for too long we assume the user killswitched * the modem at runtime. */ spin_lock(&mpwr->lock); if (!gpiod_get_value(mpwr->wakeup_gpio)) { if (ktime_ms_delta(ktime_get(), mpwr->last_wakeup) > 5000) { if (!test_and_set_bit(MPWR_F_KILLSWITCHED, mpwr->flags)) sysfs_notify(&mpwr->dev->kobj, NULL, "killswitched"); wake_up(&mpwr->wait); dev_warn(mpwr->dev, "modem looks killswitched at runtime!\n"); } } spin_unlock(&mpwr->lock); mod_timer(t, jiffies + msecs_to_jiffies(1000)); } // {{{ sysfs static ssize_t powered_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mpwr_dev *mpwr = platform_get_drvdata(to_platform_device(dev)); return scnprintf(buf, PAGE_SIZE, "%u\n", !!test_bit(MPWR_F_POWERED, mpwr->flags)); } static ssize_t powered_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { struct mpwr_dev *mpwr = platform_get_drvdata(to_platform_device(dev)); unsigned status; int ret; if (test_bit(MPWR_F_BLOCKED, mpwr->flags)) return -EPERM; ret = kstrtouint(buf, 10, &status); if (ret) return ret; mpwr_request_power_change(mpwr, status ? MPWR_REQ_PWUP : MPWR_REQ_PWDN, status); return len; } static ssize_t powered_blocking_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { struct mpwr_dev *mpwr = platform_get_drvdata(to_platform_device(dev)); unsigned status; int ret; if (test_bit(MPWR_F_BLOCKED, mpwr->flags)) return -EPERM; ret = kstrtouint(buf, 10, &status); if (ret) return ret; mpwr_request_power_change(mpwr, status ? MPWR_REQ_PWUP : MPWR_REQ_PWDN, status); ret = wait_event_interruptible_timeout(mpwr->wait, !test_bit(MPWR_F_POWER_CHANGE_INPROGRESS, mpwr->flags), msecs_to_jiffies(60000)); if (ret <= 0) { dev_err(mpwr->dev, "Power state change timeout\n"); return -EIO; } if (!!status != !!test_bit(MPWR_F_POWERED, mpwr->flags)) return -EIO; return len; } static ssize_t help_show(struct device *dev, struct device_attribute *attr, char *buf) { return scnprintf(buf, PAGE_SIZE, "echo N > powered, where N can be:\n" "0: power off\n" "1: normal powerup\n" "2: dumb powerup (no AT commands and little error checking during powerup)\n" "3: fastboot powerup (with biktor's patched aboot - #W_DISABLE held low during powerup)\n" "4: alternate powerup (megi's userspace - DTR held high during powerup)\n\n" "echo N > powered_blocking can be used for the write to block until power status transition completes\n"); } static ssize_t killswitched_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mpwr_dev *mpwr = platform_get_drvdata(to_platform_device(dev)); return scnprintf(buf, PAGE_SIZE, "%u\n", !!test_bit(MPWR_F_KILLSWITCHED, mpwr->flags)); } static ssize_t is_busy_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mpwr_dev *mpwr = platform_get_drvdata(to_platform_device(dev)); return scnprintf(buf, PAGE_SIZE, "%u\n", !!test_bit(MPWR_F_POWER_CHANGE_INPROGRESS, mpwr->flags)); } static ssize_t hard_reset_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { struct mpwr_dev *mpwr = platform_get_drvdata(to_platform_device(dev)); bool val; int ret; if (test_bit(MPWR_F_BLOCKED, mpwr->flags)) return -EPERM; ret = kstrtobool(buf, &val); if (ret) return ret; if (val) mpwr_request_power_change(mpwr, MPWR_REQ_RESET, -1); return len; } static ssize_t debug_pins_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { struct mpwr_dev *mpwr = platform_get_drvdata(to_platform_device(dev)); unsigned val; int ret; if (test_bit(MPWR_F_BLOCKED, mpwr->flags)) return -EPERM; ret = kstrtouint(buf, 16, &val); if (ret) return ret; gpiod_direction_output(mpwr->host_ready_gpio, val & BIT(0)); gpiod_direction_output(mpwr->enable_gpio, val & BIT(1)); gpiod_direction_output(mpwr->dtr_gpio, val & BIT(2)); gpiod_direction_output(mpwr->rts_gpio, val & BIT(3)); return len; } static ssize_t debug_pins_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mpwr_dev *mpwr = platform_get_drvdata(to_platform_device(dev)); return scnprintf(buf, PAGE_SIZE, "CTS=%u RI=%u\n", gpiod_get_value(mpwr->cts_gpio), gpiod_get_value(mpwr->wakeup_gpio)); } static DEVICE_ATTR_RW(powered); static DEVICE_ATTR_WO(powered_blocking); static DEVICE_ATTR_RO(killswitched); static DEVICE_ATTR_RO(is_busy); static DEVICE_ATTR_RO(help); static DEVICE_ATTR_WO(hard_reset); static DEVICE_ATTR_RW(debug_pins); static struct attribute *mpwr_attrs[] = { &dev_attr_powered.attr, &dev_attr_powered_blocking.attr, &dev_attr_killswitched.attr, &dev_attr_is_busy.attr, &dev_attr_help.attr, &dev_attr_hard_reset.attr, &dev_attr_debug_pins.attr, NULL, }; static const struct attribute_group mpwr_group = { .attrs = mpwr_attrs, }; // }}} // {{{ rfkill static int mpwr_rfkill_set(void *data, bool blocked) { struct mpwr_dev *mpwr = data; gpiod_set_value(mpwr->enable_gpio, !blocked); return 0; } static void mpwr_rfkill_query(struct rfkill *rfkill, void *data) { struct mpwr_dev *mpwr = data; rfkill_set_sw_state(rfkill, !gpiod_get_value(mpwr->enable_gpio)); } static const struct rfkill_ops mpwr_rfkill_ops = { .set_block = mpwr_rfkill_set, .query = mpwr_rfkill_query, }; // }}} // {{{ probe static int mpwr_probe_generic(struct device *dev, struct mpwr_dev **mpwr_out) { struct mpwr_dev *mpwr; struct device_node *np = dev->of_node; struct device *sdev; const char* cdev_name = NULL; int ret, i; mpwr = devm_kzalloc(dev, sizeof(*mpwr), GFP_KERNEL); if (!mpwr) return -ENOMEM; mpwr->variant = of_device_get_match_data(dev); if (!mpwr->variant) return -EINVAL; mpwr->dev = dev; init_waitqueue_head(&mpwr->wait); mutex_init(&mpwr->modem_lock); spin_lock_init(&mpwr->lock); INIT_WORK(&mpwr->power_work, &mpwr_work_handler); INIT_WORK(&mpwr->finish_pdn_work, &mpwr_finish_pdn_work); INIT_DELAYED_WORK(&mpwr->host_ready_work, mpwr_host_ready_work); INIT_KFIFO(mpwr->kfifo); ret = of_property_read_string(np, "char-device-name", &cdev_name); if (ret) { dev_err(dev, "char-device-name is not configured"); return -EINVAL; } if (of_property_read_bool(np, "blocked")) set_bit(MPWR_F_BLOCKED, mpwr->flags); mpwr->status_pwrkey_multiplexed = of_property_read_bool(np, "status-pwrkey-multiplexed"); mpwr->regulator = devm_regulator_get_optional(dev, "power"); if (IS_ERR(mpwr->regulator)) { ret = PTR_ERR(mpwr->regulator); if (ret != -ENODEV) { dev_err(dev, "can't get power supply err=%d", ret); return ret; } mpwr->regulator = NULL; } if (!mpwr->regulator && mpwr->variant->regulator_required) { dev_err(dev, "can't get power supply err=%d", -ENODEV); return -ENODEV; } mpwr->regulator_vbus = devm_regulator_get(dev, "vbus"); if (IS_ERR(mpwr->regulator_vbus)) return dev_err_probe(dev, PTR_ERR(mpwr->regulator_vbus), "can't get vbus power supply\n"); for (i = 0; mpwr->variant->gpios[i].name; i++) { const struct mpwr_gpio *io = &mpwr->variant->gpios[i]; struct gpio_desc **desc = (struct gpio_desc **)((u8*)mpwr + io->desc_off); int *irq = (int*)((u8*)mpwr + io->irq_off); char buf[64]; if (io->required) *desc = devm_gpiod_get(dev, io->name, io->flags); else *desc = devm_gpiod_get_optional(dev, io->name, io->flags); if (IS_ERR(*desc)) { dev_err(dev, "can't get %s gpio err=%ld", io->name, PTR_ERR(*desc)); return PTR_ERR(*desc); } if (!*desc) continue; if (io->irq_flags == 0 || io->irq_off == 0) continue; *irq = gpiod_to_irq(*desc); if (*irq <= 0) { dev_err(dev, "error converting %s gpio to irq: %d", io->name, ret); return *irq; } snprintf(buf, sizeof buf, "modem-%s-gpio", io->name); ret = devm_request_irq(dev, *irq, mpwr_gpio_isr, io->irq_flags, devm_kstrdup(dev, buf, GFP_KERNEL), mpwr); if (ret) { dev_err(dev, "error requesting %s irq: %d", io->name, ret); return ret; } } if (mpwr->status_pwrkey_multiplexed && mpwr->pwrkey_gpio) { dev_err(dev, "status and pwrkey are multiplexed, but pwrkey defined\n"); return -EINVAL; } if (mpwr->status_pwrkey_multiplexed && !mpwr->status_gpio) { dev_err(dev, "status and pwrkey are multiplexed, but status is not defined\n"); return -EINVAL; } ret = devm_device_add_group(dev, &mpwr_group); if (ret) return ret; // create char device ret = alloc_chrdev_region(&mpwr->major, 0, 1, "modem-power"); if (ret) { dev_err(dev, "can't allocate chrdev region"); goto err_disable_regulator; } cdev_init(&mpwr->cdev, &mpwr_fops); mpwr->cdev.owner = THIS_MODULE; ret = cdev_add(&mpwr->cdev, mpwr->major, 1); if (ret) { dev_err(dev, "can't add cdev"); goto err_unreg_chrev_region; } sdev = device_create(mpwr_class, dev, mpwr->major, mpwr, cdev_name); if (IS_ERR(sdev)) { ret = PTR_ERR(sdev); goto err_del_cdev; } if (mpwr->wakeup_irq > 0) { ret = device_init_wakeup(dev, true); if (ret) { dev_err(dev, "failed to init wakeup (%d)\n", ret); goto err_free_dev; } } if (mpwr->enable_gpio) { mpwr->rfkill = rfkill_alloc("modem", dev, RFKILL_TYPE_WWAN, &mpwr_rfkill_ops, mpwr); if (!mpwr->rfkill) { dev_err(dev, "failed to alloc rfkill\n"); ret = -ENOMEM; goto err_deinit_wakeup; } rfkill_init_sw_state(mpwr->rfkill, false); ret = rfkill_register(mpwr->rfkill); if (ret) { dev_err(dev, "failed to register rfkill (%d)\n", ret); goto err_free_rfkill; } } mpwr->wq = alloc_ordered_workqueue("modem-power", 0); if (!mpwr->wq) { ret = -ENOMEM; dev_err(dev, "failed to allocate workqueue\n"); goto err_unreg_rfkill; } if (mpwr->variant->power_init) mpwr->variant->power_init(mpwr); timer_setup(&mpwr->wd_timer, mpwr_wd_timer_fn, 0); mod_timer(&mpwr->wd_timer, jiffies + msecs_to_jiffies(50)); dev_info(dev, "modem power manager ready"); *mpwr_out = mpwr; return 0; err_unreg_rfkill: if (mpwr->rfkill) rfkill_unregister(mpwr->rfkill); err_free_rfkill: if (mpwr->rfkill) rfkill_destroy(mpwr->rfkill); err_deinit_wakeup: if (mpwr->wakeup_irq > 0) device_init_wakeup(dev, false); err_free_dev: device_destroy(mpwr_class, mpwr->major); err_del_cdev: cdev_del(&mpwr->cdev); err_unreg_chrev_region: unregister_chrdev(mpwr->major, "modem-power"); err_disable_regulator: cancel_work_sync(&mpwr->power_work); return ret; } static int mpwr_remove_generic(struct mpwr_dev *mpwr) { if (mpwr->rfkill) { rfkill_unregister(mpwr->rfkill); rfkill_destroy(mpwr->rfkill); } if (mpwr->wakeup_irq > 0) device_init_wakeup(mpwr->dev, false); del_timer_sync(&mpwr->wd_timer); cancel_delayed_work_sync(&mpwr->host_ready_work); cancel_work_sync(&mpwr->power_work); destroy_workqueue(mpwr->wq); mutex_lock(&mpwr->modem_lock); mpwr_power_down(mpwr); mutex_unlock(&mpwr->modem_lock); device_destroy(mpwr_class, mpwr->major); cdev_del(&mpwr->cdev); unregister_chrdev(mpwr->major, "modem-power"); return 0; } static void mpwr_shutdown_generic(struct mpwr_dev *mpwr) { cancel_work_sync(&mpwr->power_work); cancel_delayed_work_sync(&mpwr->host_ready_work); mutex_lock(&mpwr->modem_lock); mpwr_power_down(mpwr); mutex_unlock(&mpwr->modem_lock); } // }}} // {{{ suspend/resume static int __maybe_unused mpwr_suspend(struct device *dev) { struct mpwr_dev *mpwr = dev_get_drvdata(dev); int ret = 0; if (!test_bit(MPWR_F_POWERED, mpwr->flags)) return 0; //if (mpwr->sleep_gpio) //gpiod_direction_output(mpwr->sleep_gpio, 1); if (mpwr->variant->suspend) mpwr->variant->suspend(mpwr); if (mpwr->wakeup_irq && device_may_wakeup(mpwr->dev)) enable_irq_wake(mpwr->wakeup_irq); return ret; } static int __maybe_unused mpwr_resume(struct device *dev) { struct mpwr_dev *mpwr = dev_get_drvdata(dev); int ret = 0; if (!test_bit(MPWR_F_POWERED, mpwr->flags)) return 0; //if (mpwr->sleep_gpio) //gpiod_direction_output(mpwr->sleep_gpio, 0); if (mpwr->variant->resume) mpwr->variant->resume(mpwr); if (mpwr->wakeup_irq && device_may_wakeup(mpwr->dev)) disable_irq_wake(mpwr->wakeup_irq); return ret; } static const struct dev_pm_ops mpwr_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(mpwr_suspend, mpwr_resume) }; // }}} // {{{ serdev static int mpwr_serdev_send_msg(struct mpwr_dev *mpwr, const char *msg) { int ret, len; char buf[128]; if (!mpwr->serdev) return -ENODEV; len = snprintf(buf, sizeof buf, "%s\r\n", msg); if (len >= sizeof buf) return -E2BIG; ret = serdev_device_write(mpwr->serdev, buf, len, msecs_to_jiffies(3000)); if (ret < len) return -EIO; serdev_device_wait_until_sent(mpwr->serdev, msecs_to_jiffies(3000)); return 0; } static int __mpwr_serdev_at_cmd(struct mpwr_dev *mpwr, const char *msg, int timeout_ms, bool report_error, bool report_timeout) { int ret; if (test_and_set_bit(MPWR_F_RECEIVING_MSG, mpwr->flags)) return -EBUSY; mpwr->msg_len = 0; dev_dbg(mpwr->dev, "SEND: %s\n", msg); ret = mpwr_serdev_send_msg(mpwr, msg); if (ret) { clear_bit(MPWR_F_RECEIVING_MSG, mpwr->flags); dev_err(mpwr->dev, "AT command '%s' can't be sent (%d)\n", msg, ret); return ret; } ret = wait_event_interruptible_timeout(mpwr->wait, !test_bit(MPWR_F_RECEIVING_MSG, mpwr->flags), msecs_to_jiffies(timeout_ms)); if (ret <= 0) { clear_bit(MPWR_F_RECEIVING_MSG, mpwr->flags); if (report_timeout) dev_err(mpwr->dev, "AT command '%s' timed out\n", msg); return ret ? ret : -ETIMEDOUT; } if (!mpwr->msg_ok) { if (report_error) dev_err(mpwr->dev, "AT command '%s' returned ERROR\n", msg); return -EINVAL; } return 0; } static int mpwr_serdev_at_cmd(struct mpwr_dev *mpwr, const char *msg, int timeout_ms) { return __mpwr_serdev_at_cmd(mpwr, msg, timeout_ms, true, true); } static int __mpwr_serdev_at_cmd_with_retry(struct mpwr_dev *mpwr, const char *msg, int timeout_ms, int tries, bool ignore_timeout) { int ret = 0; if (tries < 1) tries = 1; while (tries-- > 0) { ret = __mpwr_serdev_at_cmd(mpwr, msg, timeout_ms, false, !ignore_timeout); if (ret != -EINVAL && (!ignore_timeout || ret != -ETIMEDOUT)) return ret; if (ret != -ETIMEDOUT) msleep(1000); } dev_err(mpwr->dev, "AT command '%s' returned ERROR\n", msg); return ret; } static int mpwr_serdev_at_cmd_with_retry(struct mpwr_dev *mpwr, const char *msg, int timeout_ms, int tries) { return __mpwr_serdev_at_cmd_with_retry(mpwr, msg, timeout_ms, tries, false); } static int mpwr_serdev_at_cmd_with_retry_ignore_timeout(struct mpwr_dev *mpwr, const char *msg, int timeout_ms, int tries) { return __mpwr_serdev_at_cmd_with_retry(mpwr, msg, timeout_ms, tries, true); } static void mpwr_serdev_receive_msg(struct mpwr_dev *mpwr, const char *msg) { dev_dbg(mpwr->dev, "RECV: %s\n", msg); if (mpwr->variant->recv_msg) mpwr->variant->recv_msg(mpwr, msg); if (!test_bit(MPWR_F_RECEIVING_MSG, mpwr->flags)) return; if (!strcmp(msg, "OK")) { clear_bit(MPWR_F_RECEIVING_MSG, mpwr->flags); mpwr->msg_ok = true; wake_up(&mpwr->wait); return; } else if (!strcmp(msg, "ERROR")) { clear_bit(MPWR_F_RECEIVING_MSG, mpwr->flags); mpwr->msg_ok = false; wake_up(&mpwr->wait); return; } else { int len = strlen(msg); if (mpwr->msg_len + len + 1 > sizeof(mpwr->msg)) { dev_warn(mpwr->dev, "message buffer overflow, ignoring message\n"); return; } memcpy(mpwr->msg + mpwr->msg_len, msg, len + 1); mpwr->msg_len += len + 1; } } static int mpwr_serdev_receive_buf(struct serdev_device *serdev, const unsigned char *buf, size_t count) { struct mpwr_dev *mpwr = serdev_device_get_drvdata(serdev); size_t avail = sizeof(mpwr->rcvbuf) - mpwr->rcvbuf_fill; char* p; if (avail < count) count = avail; if (avail > 0) { memcpy(mpwr->rcvbuf + mpwr->rcvbuf_fill, buf, count); mpwr->rcvbuf_fill += count; } while (true) { p = strnstr(mpwr->rcvbuf, "\r\n", mpwr->rcvbuf_fill); if (p) { if (p > mpwr->rcvbuf) { *p = 0; mpwr_serdev_receive_msg(mpwr, mpwr->rcvbuf); } mpwr->rcvbuf_fill -= (p - mpwr->rcvbuf) + 2; memmove(mpwr->rcvbuf, p + 2, mpwr->rcvbuf_fill); } else { if (sizeof(mpwr->rcvbuf) - mpwr->rcvbuf_fill == 0) { mpwr->rcvbuf_fill = 0; dev_warn(mpwr->dev, "rcvbuf overflow\n"); } break; } } return count; } static const struct serdev_device_ops mpwr_serdev_ops = { .receive_buf = mpwr_serdev_receive_buf, .write_wakeup = serdev_device_write_wakeup, }; static int mpwr_serdev_probe(struct serdev_device *serdev) { struct device *dev = &serdev->dev; struct mpwr_dev* mpwr; int ret; ret = mpwr_probe_generic(dev, &mpwr); if (ret) return ret; serdev_device_set_drvdata(serdev, mpwr); serdev_device_set_client_ops(serdev, &mpwr_serdev_ops); mpwr->serdev = serdev; return 0; } static void mpwr_serdev_remove(struct serdev_device *serdev) { struct mpwr_dev *mpwr = serdev_device_get_drvdata(serdev); mpwr_remove_generic(mpwr); } static const struct of_device_id mpwr_of_match_serdev[] = { { .compatible = "quectel,eg25", .data = &mpwr_eg25_variant }, {}, }; MODULE_DEVICE_TABLE(of, mpwr_of_match_serdev); static void mpwr_serdev_shutdown(struct device *dev) { struct mpwr_dev *mpwr = dev_get_drvdata(dev); mpwr_shutdown_generic(mpwr); } static struct serdev_device_driver mpwr_serdev_driver = { .probe = mpwr_serdev_probe, .remove = mpwr_serdev_remove, .driver = { .name = DRIVER_NAME, .of_match_table = mpwr_of_match_serdev, .pm = &mpwr_pm_ops, .shutdown = mpwr_serdev_shutdown, }, }; // }}} // {{{ platdev static int mpwr_pdev_probe(struct platform_device *pdev) { struct mpwr_dev* mpwr; int ret; ret = mpwr_probe_generic(&pdev->dev, &mpwr); if (ret) return ret; platform_set_drvdata(pdev, mpwr); return 0; } static int mpwr_pdev_remove(struct platform_device *pdev) { struct mpwr_dev *mpwr = platform_get_drvdata(pdev); return mpwr_remove_generic(mpwr); } static void mpwr_pdev_shutdown(struct platform_device *pdev) { struct mpwr_dev *mpwr = platform_get_drvdata(pdev); mpwr_shutdown_generic(mpwr); } static const struct of_device_id mpwr_of_match_plat[] = { { .compatible = "zte,mg3732", .data = &mpwr_mg2723_variant }, {}, }; MODULE_DEVICE_TABLE(of, mpwr_of_match_plat); static struct platform_driver mpwr_platform_driver = { .probe = mpwr_pdev_probe, .remove = mpwr_pdev_remove, .shutdown = mpwr_pdev_shutdown, .driver = { .name = DRIVER_NAME, .of_match_table = mpwr_of_match_plat, .pm = &mpwr_pm_ops, }, }; // }}} // {{{ driver init static int __init mpwr_driver_init(void) { int ret; mpwr_class = class_create("modem-power"); if (IS_ERR(mpwr_class)) return PTR_ERR(mpwr_class); ret = serdev_device_driver_register(&mpwr_serdev_driver); if (ret) goto err_class; ret = platform_driver_register(&mpwr_platform_driver); if (ret) goto err_serdev; return ret; err_serdev: serdev_device_driver_unregister(&mpwr_serdev_driver); err_class: class_destroy(mpwr_class); return ret; } static void __exit mpwr_driver_exit(void) { serdev_device_driver_unregister(&mpwr_serdev_driver); platform_driver_unregister(&mpwr_platform_driver); class_destroy(mpwr_class); } module_init(mpwr_driver_init); module_exit(mpwr_driver_exit); MODULE_DESCRIPTION("Modem power manager"); MODULE_AUTHOR("Ondrej Jirman "); MODULE_LICENSE("GPL v2"); // }}}