+
+ return ret;
+}
+
+/*
+ * Get the device's current state. Exhaustively, relentlessly.
+ * Concentrate all details of communication in the physical transport,
+ * register layout interpretation, and potential model dependency in
+ * this central spot, to simplify maintenance.
+ *
+ * TODO Optionally limit the transfer volume depending on caller's spec
+ * which detail level is desired? Is 10 registers each 16bits an issue
+ * when the UART bitrate is only 9600bps?
+ */
+SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi,
+ struct rdtech_dps_state *state)
+{
+ struct dev_context *devc;
+ struct sr_modbus_dev_inst *modbus;
+ uint16_t registers[REG_ENABLE + 1 - REG_USET];
+ int ret;
+ const uint8_t *rdptr;
+ uint16_t uset_raw, iset_raw, uout_raw, iout_raw, power_raw;
+ uint16_t reg_val, reg_state, out_state, ovpset_raw, ocpset_raw;
+ gboolean is_lock, is_out_enabled, is_reg_cc;
+ gboolean uses_ovp, uses_ocp;
+ float volt_target, curr_limit;
+ float ovp_threshold, ocp_threshold;
+ float curr_voltage, curr_current, curr_power;
+
+ if (!sdi || !sdi->priv || !sdi->conn)
+ return SR_ERR_ARG;
+ devc = sdi->priv;
+ modbus = sdi->conn;
+ if (!state)
+ return SR_ERR_ARG;
+
+ /* Transfer a chunk of registers in a single call. */
+ g_mutex_lock(&devc->rw_mutex);
+ ret = rdtech_dps_read_holding_registers(modbus,
+ REG_USET, ARRAY_SIZE(registers), registers);
+ g_mutex_unlock(&devc->rw_mutex);
+ if (ret != SR_OK)
+ return ret;
+
+ /* Interpret the registers' values. */
+ rdptr = (const void *)registers;
+ uset_raw = read_u16le_inc(&rdptr);
+ volt_target = uset_raw / devc->voltage_multiplier;
+ iset_raw = read_u16le_inc(&rdptr);
+ curr_limit = iset_raw / devc->current_multiplier;
+ uout_raw = read_u16le_inc(&rdptr);
+ curr_voltage = uout_raw / devc->voltage_multiplier;
+ iout_raw = read_u16le_inc(&rdptr);
+ curr_current = iout_raw / devc->current_multiplier;
+ power_raw = read_u16le_inc(&rdptr);
+ curr_power = power_raw / 100.0f;
+ (void)read_u16le_inc(&rdptr); /* UIN */
+ reg_val = read_u16le_inc(&rdptr); /* LOCK */
+ is_lock = reg_val != 0;
+ reg_val = read_u16le_inc(&rdptr); /* PROTECT */
+ uses_ovp = reg_val == STATE_OVP;
+ uses_ocp = reg_val == STATE_OCP;
+ reg_state = read_u16le_inc(&rdptr); /* CV_CC */
+ is_reg_cc = reg_state == MODE_CC;
+ out_state = read_u16le_inc(&rdptr); /* ENABLE */
+ is_out_enabled = out_state != 0;
+
+ /*
+ * Transfer another chunk of registers in a single call.
+ * TODO Unfortunately this call site open codes a fixed number
+ * of registers to read. But there is already some leakage of
+ * the register layout in this routine, and adding more device
+ * types in the future will make things "worse". So accept it.
+ */
+ g_mutex_lock(&devc->rw_mutex);
+ ret = rdtech_dps_read_holding_registers(modbus,
+ PRE_OVPSET, 2, registers);
+ g_mutex_unlock(&devc->rw_mutex);
+ if (ret != SR_OK)
+ return ret;
+
+ /* Interpret the registers' values. */
+ rdptr = (const void *)registers;
+ ovpset_raw = read_u16le_inc(&rdptr); /* PRE OVPSET */
+ ovp_threshold = ovpset_raw * devc->voltage_multiplier;
+ ocpset_raw = read_u16le_inc(&rdptr); /* PRE OCPSET */
+ ocp_threshold = ocpset_raw * devc->current_multiplier;
+
+ /* Store gathered details in the high level container. */
+ memset(state, 0, sizeof(*state));
+ state->lock = is_lock;
+ state->mask |= STATE_LOCK;
+ state->output_enabled = is_out_enabled;
+ state->mask |= STATE_OUTPUT_ENABLED;
+ state->regulation_cc = is_reg_cc;
+ state->mask |= STATE_REGULATION_CC;
+ state->protect_ovp = uses_ovp;
+ state->mask |= STATE_PROTECT_OVP;
+ state->protect_ocp = uses_ocp;
+ state->mask |= STATE_PROTECT_OCP;
+ state->protect_enabled = TRUE;
+ state->mask |= STATE_PROTECT_ENABLED;
+ state->voltage_target = volt_target;
+ state->mask |= STATE_VOLTAGE_TARGET;
+ state->current_limit = curr_limit;
+ state->mask |= STATE_CURRENT_LIMIT;
+ state->ovp_threshold = ovp_threshold;
+ state->mask |= STATE_OVP_THRESHOLD;
+ state->ocp_threshold = ocp_threshold;
+ state->mask |= STATE_OCP_THRESHOLD;
+ state->voltage = curr_voltage;
+ state->mask |= STATE_VOLTAGE;
+ state->current = curr_current;
+ state->mask |= STATE_CURRENT;
+ state->power = curr_power;
+ state->mask |= STATE_POWER;
+
+ return SR_OK;
+}
+
+/* Setup device's parameters. Selectively, from caller specs. */
+SR_PRIV int rdtech_dps_set_state(const struct sr_dev_inst *sdi,
+ struct rdtech_dps_state *state)
+{
+ struct dev_context *devc;
+ uint16_t reg_value;
+ int ret;
+
+ if (!sdi || !sdi->priv || !sdi->conn)
+ return SR_ERR_ARG;
+ devc = sdi->priv;
+ if (!state)
+ return SR_ERR_ARG;
+
+ /* Only a subset of known values is settable. */
+ if (state->mask & STATE_OUTPUT_ENABLED) {
+ reg_value = state->output_enabled ? 1 : 0;
+ ret = rdtech_dps_set_reg(sdi, REG_ENABLE, reg_value);
+ if (ret != SR_OK)
+ return ret;
+ }
+ if (state->mask & STATE_VOLTAGE_TARGET) {
+ reg_value = state->voltage_target * devc->voltage_multiplier;
+ ret = rdtech_dps_set_reg(sdi, REG_USET, reg_value);
+ if (ret != SR_OK)
+ return ret;
+ }
+ if (state->mask & STATE_CURRENT_LIMIT) {
+ reg_value = state->current_limit * devc->current_multiplier;
+ ret = rdtech_dps_set_reg(sdi, REG_ISET, reg_value);
+ if (ret != SR_OK)
+ return ret;
+ }
+ if (state->mask & STATE_OVP_THRESHOLD) {
+ reg_value = state->ovp_threshold * devc->voltage_multiplier;
+ ret = rdtech_dps_set_reg(sdi, PRE_OVPSET, reg_value);
+ if (ret != SR_OK)
+ return ret;
+ }
+ if (state->mask & STATE_OCP_THRESHOLD) {
+ reg_value = state->ocp_threshold * devc->current_multiplier;
+ ret = rdtech_dps_set_reg(sdi, PRE_OCPSET, reg_value);
+ if (ret != SR_OK)
+ return ret;
+ }
+ if (state->mask & STATE_LOCK) {
+ reg_value = state->lock ? 1 : 0;
+ ret = rdtech_dps_set_reg(sdi, REG_LOCK, reg_value);
+ if (ret != SR_OK)
+ return ret;
+ }
+
+ return SR_OK;