diff --git a/data/meson.build b/data/meson.build index 56bc6e3..f1fc8f0 100644 --- a/data/meson.build +++ b/data/meson.build @@ -8,6 +8,7 @@ conf_files = [ 'pine64,pinephone-1.0.toml', 'pine64,pinephone-1.1.toml', 'pine64,pinephone-1.2.toml', +'pine64,pinephone-pro.toml', ] install_data(conf_files) diff --git a/data/pine64,pinephone-1.0.toml b/data/pine64,pinephone-1.0.toml index 8b91a88..10dbc1e 100644 --- a/data/pine64,pinephone-1.0.toml +++ b/data/pine64,pinephone-1.0.toml @@ -13,11 +13,12 @@ poweron_delay = 100000 #recovery_timeout = 9 [gpio] -dtr = 358 -pwrkey = 35 -reset = 68 -apready = 231 -disable = 232 +chips = [ "1c20800.pinctrl", "1f02c00.pinctrl" ] +dtr = { chip = 1, line = 6 } +pwrkey = { chip = 0, line = 35 } +reset = { chip = 0, line = 68 } +apready = { chip = 0, line = 231 } +disable = { chip = 0, line = 232 } [at] uart = "/dev/ttyS2" diff --git a/data/pine64,pinephone-1.1.toml b/data/pine64,pinephone-1.1.toml index fd09462..9242a91 100644 --- a/data/pine64,pinephone-1.1.toml +++ b/data/pine64,pinephone-1.1.toml @@ -13,11 +13,12 @@ poweron_delay = 100000 #recovery_timeout = 9 [gpio] -dtr = 358 -pwrkey = 35 -reset = 68 -apready = 231 -disable = 232 +chips = [ "1c20800.pinctrl", "1f02c00.pinctrl" ] +dtr = { chip = 1, line = 6 } +pwrkey = { chip = 0, line = 35 } +reset = { chip = 0, line = 68 } +apready = { chip = 0, line = 231 } +disable = { chip = 0, line = 232 } [at] uart = "/dev/ttyS2" diff --git a/data/pine64,pinephone-1.2.toml b/data/pine64,pinephone-1.2.toml index 4244bee..3754274 100644 --- a/data/pine64,pinephone-1.2.toml +++ b/data/pine64,pinephone-1.2.toml @@ -9,12 +9,13 @@ poweron_delay = 100000 #recovery_timeout = 9 [gpio] -dtr = 34 -pwrkey = 35 -reset = 68 -apready = 231 -disable = 232 -status = 233 +chips = [ "1c20800.pinctrl" ] +dtr = { chip = 0, line = 34 } +pwrkey = { chip = 0, line = 35 } +reset = { chip = 0, line = 68 } +apready = { chip = 0, line = 231 } +disable = { chip = 0, line = 232 } +status = { chip = 0, line = 233 } [at] uart = "/dev/ttyS2" diff --git a/data/pine64,pinephone-pro.toml b/data/pine64,pinephone-pro.toml new file mode 100644 index 0000000..492de96 --- /dev/null +++ b/data/pine64,pinephone-pro.toml @@ -0,0 +1,97 @@ +[manager] +# Delay between setting GPIO and PWRKEY sequence, set in microseconds +poweron_delay = 100000 + +# Uncomment the following if you need to change the modem detection timeout on +# resume and/or the time during which suspend is blocked after modem boot +#[suspend] +#boot_timeout = 120 +#recovery_timeout = 9 + +[gpio] +chips = [ "gpio0", "gpio3" ] +dtr = { chip = 0, line = 3 } +pwrkey = { chip = 0, line = 13 } +reset = { chip = 1, line = 8 } +apready = { chip = 0, line = 12 } +disable = { chip = 0, line = 8 } +status = { chip = 1, line = 6 } + +[at] +uart = "/dev/ttyS3" +configure = [ +# Each command has 4 possible elements: +# * `cmd` : the AT command itself, which will be translated to "AT+`cmd`" +# * `subcmd`: the subcommand in case a single AT command can be used +# to change multiple parameters, such as QCFG (optional) +# * `value` : the commands, argument, usually used to set the value of +# a specific parameter (optional) +# * `expect`: the expected return value; the command is first executed +# without any value in order to query the current state. This +# state is then compared to the `expect` string; if they don't +# match, the command is then executed with value `expect` in +# order to set the parameter to the configured value (optional) +# A command can have `expect` OR `value` configured, but it shouldn't have both +# Print software version + { cmd = "QGMR" }, +# Configure audio + { cmd = "QDAI", expect = "3,0,0,4,0,0,1,1" }, +# RI signaling using physical RI pin + { cmd = "QCFG", subcmd = "risignaltype", expect = "\"physical\"" }, +# Enable VoLTE support + { cmd = "QCFG", subcmd = "ims", expect = "1" }, +# Enable APREADY for PP 1.2 + { cmd = "QCFG", subcmd = "apready", expect = "1,0,500" }, +# URC configuration for PP 1.2 (APREADY pin connected): +# * RING URC: normal pulse length +# * Incoming SMS URC: default pulse length +# * Other URC: default length +# * Report URCs on all ports (serial and USB) for FOSS firmware +# * Reporting of URCs without any delay +# * Configure URC pin to UART Ring Indicator + { cmd = "QCFG", subcmd = "urc/ri/ring", expect = "\"pulse\",120,1000,5000,\"off\",1" }, + { cmd = "QCFG", subcmd = "urc/ri/smsincoming", expect = "\"pulse\",120,1" }, + { cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1,1" }, + { cmd = "QCFG", subcmd = "urc/delay", expect = "0" }, + { cmd = "QCFG", subcmd = "urc/cache", expect = "0" }, + { cmd = "QCFG", subcmd = "urc/ri/pin", expect = "uart_ri" }, + { cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" }, +# Allow sleeping for power saving + { cmd = "QSCLK", value = "1" }, +# GNSS configuration: +# * Enable A-GPS data upload support (XTRA) +# * Disable On-Demand-Positioning (ODP) mode +# to avoid running the GNSS system in the background, even when not enabled. +# * Enable Dynamic Power Optimizations (DPO) mode to turn off GNSS RF radios +# when they are not in use. +# * Only enable GPS and GLONASS, disable other GNSS systems. +# A-GPS data upload doesn't work for Galileo anyway. +# * Avoid turning on GNSS support automatically when the modem boots. + { cmd = "QGPSXTRA", expect = "1" }, + { cmd = "QGPSCFG", subcmd = "gnssconfig", expect = "4" }, + { cmd = "QGPSCFG", subcmd = "odpcontrol", expect = "0" }, + { cmd = "QGPSCFG", subcmd = "dpoenable", expect = "1" }, + { cmd = "QGPSCFG", subcmd = "gpsnmeatype", expect = "31" }, + { cmd = "QGPSCFG", subcmd = "glonassnmeatype", expect = "7" }, + { cmd = "QGPSCFG", subcmd = "galileonmeatype", expect = "0" }, + { cmd = "QGPSCFG", subcmd = "beidounmeatype", expect = "0" }, + { cmd = "QGPSCFG", subcmd = "autogps", expect = "0" }, +# Disable fast poweroff for stability + { cmd = "QCFG", subcmd = "fast/poweroff", expect = "0" }, +# Configure sleep and wake up pin levels to active low + { cmd = "QCFG", subcmd = "sleepind/level", expect = "0" }, + { cmd = "QCFG", subcmd = "wakeupin/level", expect = "0,0" }, +# Do not enter RAMDUMP mode, auto-reset instead + { cmd = "QCFG", subcmd = "ApRstLevel", expect = "1" }, + { cmd = "QCFG", subcmd = "ModemRstLevel", expect = "1" }, +] +suspend = [ +] +resume = [ +] +reset = [ { cmd = "CFUN", value = "1,1" } ] + +[gnss] +enabled = true +url = "https://xtrapath4.izatcloud.net" +file = "xtra2.bin" diff --git a/meson.build b/meson.build index d8d2202..535927a 100644 --- a/meson.build +++ b/meson.build @@ -8,7 +8,7 @@ project ( 'eg25-manager', 'c', - version : '0.4.1', + version : '0.4.2', license : 'GPLv3+', meson_version : '>= 0.50.0', default_options : diff --git a/src/at.c b/src/at.c index ee99ff4..56dbe76 100644 --- a/src/at.c +++ b/src/at.c @@ -245,7 +245,14 @@ static gboolean modem_response(gint fd, */ do { ret = read(fd, tmp, sizeof(tmp)); + if (ret > 0) { + /* If we're going to overflow truncate the data we read to fit */ + if (pos + ret >= sizeof(response)) { + g_critical("AT response buffer full, truncating"); + ret = sizeof(response) - (pos + 1); + } + memcpy(&response[pos], tmp, ret); pos += ret; usleep(10000); diff --git a/src/config.c b/src/config.c index d4ce151..2c5aafa 100644 --- a/src/config.c +++ b/src/config.c @@ -81,3 +81,17 @@ gboolean config_get_array(toml_table_t **config, const gchar *key, toml_array_t return !!array; } + +gboolean config_get_table(toml_table_t **config, const gchar *key, toml_table_t **result) +{ + toml_table_t *table = NULL; + + if (config[EG25_CONFIG_USER]) + table = toml_table_in(config[EG25_CONFIG_USER], key); + if (!table) + table = toml_table_in(config[EG25_CONFIG_SYS], key); + if (table && result) + *result = table; + + return !!table; +} diff --git a/src/config.h b/src/config.h index d1a214f..5f2d1e2 100644 --- a/src/config.h +++ b/src/config.h @@ -24,3 +24,4 @@ gboolean config_get_int(toml_table_t **config, const gchar *key, gint *result); gboolean config_get_uint(toml_table_t **config, const gchar *key, guint *result); gboolean config_get_string(toml_table_t **config, const gchar *key, gchar **result); gboolean config_get_array(toml_table_t **config, const gchar *key, toml_array_t **result); +gboolean config_get_table(toml_table_t **config, const gchar *key, toml_table_t **result); diff --git a/src/gpio.c b/src/gpio.c index c7cfc53..49bee0d 100644 --- a/src/gpio.c +++ b/src/gpio.c @@ -9,9 +9,9 @@ #include +/* Those defines are used for legacy config files only */ #define GPIO_CHIP1_LABEL "1c20800.pinctrl" #define GPIO_CHIP2_LABEL "1f02c00.pinctrl" - #define MAX_GPIOCHIP_LINES 352 enum { @@ -101,10 +101,41 @@ int gpio_sequence_sleep(struct EG25Manager *manager) return 0; } +struct gpiod_line *gpio_get_output_line(struct EG25Manager *manager, int chip, int line) +{ + struct gpiod_line *gpio_line; + + gpio_line = gpiod_chip_get_line(manager->gpiochip[chip], line); + if (!gpio_line) + return NULL; + + if (gpiod_line_request_output(gpio_line, "eg25manager", 0) < 0) { + gpiod_line_release(gpio_line); + return NULL; + } + + return gpio_line; +} + +struct gpiod_line *gpio_get_input_line(struct EG25Manager *manager, int chip, int line) +{ + struct gpiod_line *gpio_line; + + gpio_line = gpiod_chip_get_line(manager->gpiochip[chip], line); + if (!gpio_line) + return NULL; + + if (gpiod_line_request_input(gpio_line, "eg25manager") < 0) { + gpiod_line_release(gpio_line); + return NULL; + } + + return gpio_line; +} + int gpio_init(struct EG25Manager *manager, toml_table_t *config[]) { - int i, ret; - guint offset, chipidx, gpio_idx; + int i; toml_table_t *gpio_config[EG25_CONFIG_COUNT]; for (i = 0; i < EG25_CONFIG_COUNT; i++) @@ -113,65 +144,117 @@ int gpio_init(struct EG25Manager *manager, toml_table_t *config[]) if (!gpio_config[EG25_CONFIG_SYS]) g_error("Default config file lacks the 'gpio' section!"); - manager->gpiochip[0] = gpiod_chip_open_by_label(GPIO_CHIP1_LABEL); - if (!manager->gpiochip[0]) { - g_critical("Unable to open GPIO chip " GPIO_CHIP1_LABEL); - return 1; - } + /* + * The system config could have the `chips` key, but the user one + * could still use the old format! In order to avoid problems, we + * should use the new format only if: + * - there's no user config file + or + * - the user config file contains the `chips` key + * Otherwise we might start parsing the system config with the new + * format, but error out if user config overrides gpios using the + * old format + */ + if (!gpio_config[EG25_CONFIG_USER] || toml_array_in(gpio_config[EG25_CONFIG_USER], "chips")) + { + int numchips; + toml_array_t *chipslist = NULL; - manager->gpiochip[1] = gpiod_chip_open_by_label(GPIO_CHIP2_LABEL); - if (!manager->gpiochip[1]) { - g_critical("Unable to open GPIO chip " GPIO_CHIP2_LABEL); - return 1; - } + config_get_array(gpio_config, "chips", &chipslist); + numchips = toml_array_nelem(chipslist); + if (numchips > 2) + g_error("Requesting too many GPIO chips!"); - for (i = 0; i < GPIO_OUT_COUNT; i++) { - if (!config_get_uint(gpio_config, gpio_out_names[i], &gpio_idx)) - g_error("Unable to get config for output GPIO '%s'", gpio_out_names[i]); - - if (gpio_idx < MAX_GPIOCHIP_LINES) { - offset = gpio_idx; - chipidx = 0; - } else { - offset = gpio_idx - MAX_GPIOCHIP_LINES; - chipidx = 1; + for (i = 0; i < numchips; i++) { + toml_datum_t data = toml_string_at(chipslist, i); + if (!data.ok) + continue; + manager->gpiochip[i] = gpiod_chip_open_by_label(data.u.s); + if (!manager->gpiochip[i]) + g_error("Unable to find GPIO chip '%s'", data.u.s); } - manager->gpio_out[i] = gpiod_chip_get_line(manager->gpiochip[chipidx], offset); - if (!manager->gpio_out[i]) { - g_error("Unable to get output GPIO %d", i); - return 1; + for (i = 0; i < GPIO_OUT_COUNT; i++) { + toml_table_t *table; + toml_datum_t chip, line; + if (!config_get_table(gpio_config, gpio_out_names[i], &table)) + g_error("Unable to get config for output GPIO '%s'", gpio_out_names[i]); + + chip = toml_int_in(table, "chip"); + if (!chip.ok || chip.u.i < 0 || chip.u.i > 2) + g_error("Wrong chip ID for output GPIO '%s'", gpio_out_names[i]); + + line = toml_int_in(table, "line"); + if (!line.ok || line.u.i < 0 || line.u.i > gpiod_chip_num_lines(manager->gpiochip[chip.u.i])) + g_error("Wrong line ID for output GPIO '%s'", gpio_out_names[i]); + + manager->gpio_out[i] = gpio_get_output_line(manager, chip.u.i, line.u.i); + if (!manager->gpio_out[i]) + g_error("Unable to get output GPIO %d", i); } - ret = gpiod_line_request_output(manager->gpio_out[i], "eg25manager", 0); - if (ret < 0) { - g_error("Unable to request output GPIO %d", i); - return 1; + for (i = 0; i < GPIO_IN_COUNT; i++) { + toml_table_t *table; + toml_datum_t chip, line; + if (!config_get_table(gpio_config, gpio_in_names[i], &table)) + g_error("Unable to get config for input GPIO '%s'", gpio_in_names[i]); + + chip = toml_int_in(table, "chip"); + if (!chip.ok || chip.u.i < 0 || chip.u.i > 2) + g_error("Wrong chip ID for input GPIO '%s'", gpio_in_names[i]); + + line = toml_int_in(table, "line"); + if (!line.ok || line.u.i < 0 || line.u.i > gpiod_chip_num_lines(manager->gpiochip[chip.u.i])) + g_error("Wrong line ID for input GPIO '%s'", gpio_in_names[i]); + + manager->gpio_in[i] = gpio_get_input_line(manager, chip.u.i, line.u.i); + if (!manager->gpio_in[i]) + g_error("Unable to get input GPIO %d", i); } - } + } else { + guint offset, chipidx, gpio_idx; - for (i = 0; i < GPIO_IN_COUNT; i++) { - if (!config_get_uint(gpio_config, gpio_in_names[i], &gpio_idx)) - continue; + /* Legacy config file, only used on the OG PinePhone */ + manager->gpiochip[0] = gpiod_chip_open_by_label(GPIO_CHIP1_LABEL); + if (!manager->gpiochip[0]) + g_error("Unable to open GPIO chip " GPIO_CHIP1_LABEL); - if (gpio_idx < MAX_GPIOCHIP_LINES) { - offset = gpio_idx; - chipidx = 0; - } else { - offset = gpio_idx - MAX_GPIOCHIP_LINES; - chipidx = 1; + manager->gpiochip[1] = gpiod_chip_open_by_label(GPIO_CHIP2_LABEL); + if (!manager->gpiochip[1]) + g_error("Unable to open GPIO chip " GPIO_CHIP2_LABEL); + + for (i = 0; i < GPIO_OUT_COUNT; i++) { + if (!config_get_uint(gpio_config, gpio_out_names[i], &gpio_idx)) + g_error("Unable to get config for output GPIO '%s'", gpio_out_names[i]); + + if (gpio_idx < MAX_GPIOCHIP_LINES) { + offset = gpio_idx; + chipidx = 0; + } else { + offset = gpio_idx - MAX_GPIOCHIP_LINES; + chipidx = 1; + } + + manager->gpio_out[i] = gpio_get_input_line(manager, chipidx, offset); + if (!manager->gpio_out[i]) + g_error("Unable to get output GPIO %d", i); } - manager->gpio_in[i] = gpiod_chip_get_line(manager->gpiochip[chipidx], offset); - if (!manager->gpio_in[i]) { - g_warning("Unable to get input GPIO %d", i); - continue; - } + for (i = 0; i < GPIO_IN_COUNT; i++) { + if (!config_get_uint(gpio_config, gpio_in_names[i], &gpio_idx)) + continue; - ret = gpiod_line_request_input(manager->gpio_in[i], "eg25manager"); - if (ret < 0) { - g_warning("Unable to request input GPIO %d", i); - manager->gpio_in[i] = NULL; + if (gpio_idx < MAX_GPIOCHIP_LINES) { + offset = gpio_idx; + chipidx = 0; + } else { + offset = gpio_idx - MAX_GPIOCHIP_LINES; + chipidx = 1; + } + + manager->gpio_in[i] = gpio_get_input_line(manager, chipidx, offset); + if (!manager->gpio_in[i]) + g_error("Unable to get input GPIO %d", i); } }