osmo-opencm3-projects/projects/rfdsatt/attenuator.c

153 lines
4.0 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 "attenuator.h"
#include "utils.h"
static const struct attenuator_cfg *g_att_cfg;
static struct attenuator_state **g_att_state;
static void delay()
{
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);
}
/* set a given attenuator to a given value
* \param channel The RF channel (0..7)
* \param stage Attenuator stage (0..1)
* \param value in quarter-dB (0..124)
*/
int attenuator_set(uint8_t channel, uint8_t stage, uint8_t val_qdb)
{
uint8_t val = val_qdb/4; /* whole dB for this attenuator */
const struct attenuator_def *ad;
int i;
if (!g_att_cfg)
return -ENODEV;
if (channel >= g_att_cfg->num_channels)
return -ENODEV;
if (stage >= g_att_cfg->channels[channel].num_stages)
return -ENODEV;
ad = &g_att_cfg->channels[channel].stage[stage];
if (val > 0x1f)
return -ERANGE;
/* 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[channel][stage].value_qdB.current = val*4;
return 0;
}
/* get current attenuator value in quarter-dB */
int attenuator_get(uint8_t channel, uint8_t stage, enum attenuator_value av)
{
if (!g_att_cfg)
return -ENODEV;
if (channel >= g_att_cfg->num_channels)
return -ENODEV;
if (stage >= g_att_cfg->channels[channel].num_stages)
return -ENODEV;
switch (av) {
case ATT_VAL_CURRENT:
return g_att_state[channel][stage].value_qdB.current;
case ATT_VAL_STARTUP:
return g_att_state[channel][stage].value_qdB.startup;
default:
return -EINVAL;
}
}
/* 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;
}
}
}
/* 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;
rcc_periph_clock_enable(periph[k]);
gpio_set_mode(banks[k], GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, pins[k]);
}
}