269 lines
6.7 KiB
C
269 lines
6.7 KiB
C
/* Code to control digital step attenuators of the sysmocom RFDN board
|
|
* (C) 2017 by Harald Welte <laforge@gnumonks.org>
|
|
*/
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <libopencm3/stm32/gpio.h>
|
|
#include <libopencm3/stm32/rcc.h>
|
|
|
|
#include <libcommon/utils.h>
|
|
|
|
#include "attenuator.h"
|
|
|
|
static const struct attenuator_cfg *g_att_cfg;
|
|
static struct attenuator_state **g_att_state;
|
|
|
|
static void delay(void)
|
|
{
|
|
volatile uint32_t i;
|
|
for (i = 0; i < 10000; i++) { }
|
|
}
|
|
|
|
/* create a positive pulse on a given GPIO line */
|
|
static void gpio_pulse(uint32_t bank, uint32_t nr)
|
|
{
|
|
gpio_set(bank, nr);
|
|
/* FIXME: Some delay, 30ns for LE or CLK */
|
|
delay();
|
|
gpio_clear(bank, nr);
|
|
}
|
|
|
|
/* set the data line to high or low */
|
|
static void gpio_set_data(int high)
|
|
{
|
|
if (high)
|
|
gpio_set(g_att_cfg->gpio_data.bank, g_att_cfg->gpio_data.gpio_nr);
|
|
else
|
|
gpio_clear(g_att_cfg->gpio_data.bank, g_att_cfg->gpio_data.gpio_nr);
|
|
}
|
|
|
|
/* pulse the clock line with one positive pulse */
|
|
static void gpio_pulse_clk(void)
|
|
{
|
|
gpio_pulse(g_att_cfg->gpio_clock.bank, g_att_cfg->gpio_clock.gpio_nr);
|
|
}
|
|
|
|
/* validate if a channel number is within the permitted range */
|
|
static bool chan_is_valid(uint8_t channel)
|
|
{
|
|
if (channel == 0)
|
|
return false;
|
|
|
|
if (channel > g_att_cfg->num_channels)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* validate if a stage number is within the permitted range */
|
|
static bool stage_is_valid(uint8_t channel, uint8_t stage)
|
|
{
|
|
if (!chan_is_valid(channel))
|
|
return false;
|
|
|
|
if (stage == 0)
|
|
return false;
|
|
|
|
if (stage > g_att_cfg->channels[channel-1].num_stages)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* internal low-level helper */
|
|
static void _attenuator_stage_set(uint8_t channel, uint8_t stage, uint8_t val_qdb)
|
|
{
|
|
/* we assume the [internal] caller has verified those */
|
|
uint8_t chan_idx = channel-1;
|
|
uint8_t stage_idx = stage-1;
|
|
const struct attenuator_def *ad = &g_att_cfg->channels[chan_idx].stage[stage_idx];
|
|
uint8_t val;
|
|
int i;
|
|
|
|
/* the DAT-31A-SP actually has a B0 which works in half-dB steps,
|
|
* but according to the manual it must alsays be 0 */
|
|
val = val_qdb / 2;
|
|
|
|
/* The shift register should be loaded while LE is held low to
|
|
* prevent the attenuator value from changing as data is
|
|
* entered */
|
|
for (i = 5; i >= 0; i--) {
|
|
unsigned int bit = ((val >> i) & 1);
|
|
gpio_set_data(bit);
|
|
gpio_pulse_clk();
|
|
}
|
|
|
|
/* The LE input should then be toggled HIGH and brought LOW
|
|
* again, latching the new data. Minimum LE pulse width is 30ns */
|
|
gpio_pulse(ad->le.bank, ad->le.gpio_nr);
|
|
|
|
/* actual value we have set may not be exactly what was requested */
|
|
g_att_state[chan_idx][stage_idx].value_qdB.current = val_qdb;
|
|
}
|
|
|
|
/*! set a given attenuator to a given value
|
|
* \param channel The RF channel (1..8)
|
|
* \param stage Attenuator stage (1..2)
|
|
* \param val_qdb in quarter-dB (0..124)
|
|
*/
|
|
int attenuator_stage_set(uint8_t channel, uint8_t stage, uint8_t val_qdb)
|
|
{
|
|
uint8_t val = val_qdb/4; /* whole dB for this attenuator */
|
|
|
|
if (!g_att_cfg)
|
|
return -ENODEV;
|
|
|
|
if (!stage_is_valid(channel, stage))
|
|
return -ENODEV;
|
|
|
|
if (val > 0x1f)
|
|
return -ERANGE;
|
|
|
|
printf("Setting CH%u-ST%u to %u dB\r\n", channel, stage, val);
|
|
|
|
_attenuator_stage_set(channel, stage, val_qdb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*! get current attenuator value in quarter-dB
|
|
* \param channel The RF channel (1..8)
|
|
* \param stage Attenuator stage (1..2)
|
|
*/
|
|
int attenuator_stage_get(uint8_t channel, uint8_t stage, enum attenuator_value av)
|
|
{
|
|
uint8_t chan_idx;
|
|
uint8_t stage_idx;
|
|
|
|
if (!g_att_cfg)
|
|
return -ENODEV;
|
|
|
|
if (!stage_is_valid(channel, stage))
|
|
return -ENODEV;
|
|
|
|
chan_idx = channel - 1;
|
|
stage_idx = stage - 1;
|
|
|
|
switch (av) {
|
|
case ATT_VAL_CURRENT:
|
|
return g_att_state[chan_idx][stage_idx].value_qdB.current;
|
|
case ATT_VAL_STARTUP:
|
|
return g_att_state[chan_idx][stage_idx].value_qdB.startup;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/*! set an overall attenuation value for an entire channel (combination of stages)
|
|
* \param channel The RF channel (1..8)
|
|
* \param val_qdb Attenuation in quarter-dB (0..124) */
|
|
int attenuator_chan_set(uint8_t channel, uint8_t val_qdb, bool split_equal)
|
|
{
|
|
uint8_t val_a, val_b;
|
|
|
|
if (!g_att_cfg)
|
|
return -ENODEV;
|
|
|
|
if (!chan_is_valid(channel))
|
|
return -ENODEV;
|
|
|
|
if (val_qdb/4 > 0x3f)
|
|
return -ERANGE;
|
|
|
|
if (split_equal) {
|
|
/* first max out one of the two, then start using the other */
|
|
val_a = val_qdb / 2;
|
|
} else {
|
|
/* first max out one of the two, then start using the other */
|
|
if (val_qdb < 31*4)
|
|
val_a = val_qdb;
|
|
else
|
|
val_a = 31*4;
|
|
}
|
|
val_b = val_qdb - val_a; /* account for odd values */
|
|
|
|
printf("Setting CH%u to %02u + %02u = %03u dB\r\n", channel, val_a/4, val_b/4, val_qdb/4);
|
|
|
|
_attenuator_stage_set(channel, 1, val_a);
|
|
_attenuator_stage_set(channel, 2, val_b);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*! get the cumulative attenuation of all stages within one channel
|
|
* \param channel The RF channel (1..8) */
|
|
int attenuator_chan_get(uint8_t channel, enum attenuator_value av)
|
|
{
|
|
uint8_t chan_idx;
|
|
uint8_t stage;
|
|
int res = 0;
|
|
|
|
if (!g_att_cfg)
|
|
return -ENODEV;
|
|
|
|
if (!chan_is_valid(channel))
|
|
return -ENODEV;
|
|
|
|
chan_idx = channel-1;
|
|
|
|
for (stage = 1; stage <= g_att_cfg->channels[chan_idx].num_stages; stage++) {
|
|
int rc = attenuator_stage_get(channel, stage, av);
|
|
if (rc < 0)
|
|
return rc;
|
|
res += rc;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/*! initialize the attenuator driver */
|
|
void attenuator_init(const struct attenuator_cfg *cfg,
|
|
struct attenuator_state **state)
|
|
{
|
|
uint32_t banks[6] = { GPIOA, GPIOB, GPIOC, GPIOD, GPIOE, GPIOF };
|
|
const uint32_t periph[6] = { RCC_GPIOA, RCC_GPIOB, RCC_GPIOC,
|
|
RCC_GPIOD, RCC_GPIOE, RCC_GPIOF };
|
|
uint32_t pins[6] = {0, 0, 0, 0, 0, 0};
|
|
unsigned int i, j, k;
|
|
|
|
g_att_cfg = cfg;
|
|
g_att_state = state;
|
|
|
|
/* collect the bit mask of all LE pins for each bank */
|
|
for (i = 0; i < g_att_cfg->num_channels; i++) {
|
|
const struct attenuator_channel_def *ch = &g_att_cfg->channels[i];
|
|
for (j = 0; j < ch->num_stages; j++) {
|
|
const struct attenuator_def *at = &ch->stage[j];
|
|
for (k = 0; k < ARRAY_SIZE(banks); k++) {
|
|
if (at->le.bank != banks[k])
|
|
continue;
|
|
pins[k] |= at->le.gpio_nr;
|
|
//printf("CH%u ST%u: GPIO Bank %u Pin %lu\r\n", i, j, k, at->le.gpio_nr);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* add the shared DATA / CLOCK pins */
|
|
for (k = 0; k < ARRAY_SIZE(banks); k++) {
|
|
if (g_att_cfg->gpio_clock.bank == banks[k])
|
|
pins[k] |= g_att_cfg->gpio_clock.gpio_nr;
|
|
if (g_att_cfg->gpio_data.bank == banks[k])
|
|
pins[k] |= g_att_cfg->gpio_data.gpio_nr;
|
|
}
|
|
|
|
/* configure the GPIO of each bank based on he collected pin-bitmasks */
|
|
for (k = 0; k < ARRAY_SIZE(banks); k++) {
|
|
if (!pins[k])
|
|
continue;
|
|
//printf("GPIO Bank %u: 0x%08lx\r\n", k, pins[k]);
|
|
|
|
rcc_periph_clock_enable(periph[k]);
|
|
#ifdef STM32F1
|
|
gpio_set_mode(banks[k], GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, pins[k]);
|
|
#else
|
|
gpio_mode_setup(banks[k], GPIO_MODE_OUTPUT, 0, pins[k]);
|
|
#endif
|
|
gpio_clear(banks[k], pins[k]);
|
|
}
|
|
}
|