/* Code to control digital step attenuators of the sysmocom RFDN board * (C) 2017 by Harald Welte */ #include #include #include #include #include "attenuator.h" #include "utils.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); } /* 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]); } }