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