/* Cesar project {{{ * * Copyright (C) 2010 Spidcom * * <<>> * * }}} */ /** * \file bsu/aclf/src/aclf.c * \brief AC Line frequency. * \ingroup bsu_aclf * * AC Line Frequency variation module. */ #include "common/std.h" #include "mac/common/timings.h" #include "bsu/aclf/aclf.h" #include /** Store constant values with the frequency detection found. */ #define BSU_ACLF_SET_FREQUENCY(freq) \ do { \ *((bsu_aclf_frequency_t*) &ctx->frequency) \ = BSU_ACLF_FREQ_ ## freq ## HZ; \ *((bsu_aclf_bp_t*) &ctx->beacon_period_theo_tck) \ = BSU_ACLF_BP_ ## freq ## HZ_TCK; \ } while (0) /** WP History coefficient value. */ #define BSU_ACLF_WP 0.0625 /** Local variable. */ static bsu_aclf_t bsu_aclf_global; /** * Truncate the beacon period ticks computed to be suitable for BTOs. * \param ctx the module context. * * The objective is to provide a BTO with the correct value allowing the STA * to synchronise correctly. * Norm EN50160 inform that the 50Hz PowerLine may have some variation on the * frequency reaching +/- 15% for non interconnected PowerLine system. * This means that the 50Hz PowerLine frequency have a range value from 42.5Hz * to 57.5Hz. BTOs only allow a range from 48.42Hz to 51.68Hz. * This function will do the same for both frequencies i.e. 50 and 60Hz. */ static void bsu_aclf_truncate_beacon_period (bsu_aclf_t *ctx) { uint clk_min_tck = ctx->beacon_period_theo_tck - BITS_ONES (HPAV_BEACON_BTO_VALID_BITS); uint clk_max_tck = ctx->beacon_period_theo_tck + BITS_ONES (HPAV_BEACON_BTO_VALID_BITS); /* Special cases for cable mode. */ if (ctx->beacon_period_tck == 0) ctx->beacon_period_tck = BSU_ACLF_BP_50HZ_TCK; else if (ctx->beacon_period_tck > clk_max_tck) ctx->beacon_period_tck = clk_max_tck; else if (ctx->beacon_period_tck < clk_min_tck) ctx->beacon_period_tck = clk_min_tck; } /** * Compute the beacon period duration from the AC Line frequency variation. * \param ctx the module context. */ static void bsu_aclf_compute_beacon_period_from_acl (bsu_aclf_t *ctx) { uint zc_interval_tck = ctx->beacon_period_tck / 2; u32 zero_cross_date = phy_clock_get_zero_cross_captured_date (ctx->phy); u32 zc_diff_tck = zero_cross_date - ctx->zero_cross_last_date; if (zc_diff_tck) { /* Compute the number of zero cross since the last read value. */ uint zc_nb = (zc_diff_tck + zc_interval_tck / 2) / zc_interval_tck; uint bp_tck = 2 * zc_diff_tck / zc_nb; ctx->beacon_period_tck += BSU_ACLF_WP * ((int)bp_tck - (int)ctx->beacon_period_tck); bsu_aclf_truncate_beacon_period (ctx); /* Store the last zero crossing date. */ ctx->zero_cross_last_date = zero_cross_date; } } /** * Shift the date to correspond to the current date. * \param ctx the module context. * * Used when the station is acting as STA and the central beacon has not been * received. */ void bsu_aclf_shift_beacon_period_start_date (bsu_aclf_t *ctx) { uint i; dbg_assert (ctx); u32 now = phy_date (); if (lesseq_mod2p32 (ctx->bpsd[1], now)) { for (i = 0; i < BSU_ACLF_BPSD_NB - 1; i++) ctx->bpsd[i] = ctx->bpsd[i+1]; /* For the last beacon period start date, add the difference of the * previous one, this should help the station to keep a * synchronisation near the CCo's clock even if it miss 5 beacons. */ ctx->bpsd[BSU_ACLF_BPSD_NB - 1] += ctx->bpsd[BSU_ACLF_BPSD_NB - 2] - ctx->bpsd[BSU_ACLF_BPSD_NB - 3]; ctx->beacon_period_tck = ctx->bpsd[1] - ctx->bpsd[0]; } } /** * Compute the beacon period start date from the AC Line. * \param ctx the module context. */ void bsu_aclf_ac_compute_beacon_period_start_date (bsu_aclf_t *ctx) { dbg_assert (ctx); dbg_assert (ctx->aclsc); uint i; u32 now = phy_date (); u32 bts; int bto; bsu_aclf_compute_beacon_period_from_acl (ctx); /* First time the function is called. */ if (ctx->bpsd[0] == ctx->bpsd[1]) { ctx->bpsd[0] = now; for (i = 1; i < BSU_ACLF_BPSD_NB; i++) ctx->bpsd[i] = ctx->bpsd[i-1] + ctx->beacon_period_tck; } /* Compute only if the current beacon period is over. */ else if (lesseq_mod2p32 (ctx->bpsd[1], now)) { /* Update the beacon period start time. */ for (i = 0; i < BSU_ACLF_BPSD_NB - 1; i++) ctx->bpsd[i] = ctx->bpsd[i+1]; /* Add on the last beacon period start date the beacon period * estimated. */ ctx->bpsd[BSU_ACLF_BPSD_NB - 1] += ctx->beacon_period_tck; } /* Compute the BTO using the theoretical beacon period value. * BTO is computed from bpsd[1]. */ bts = ctx->bpsd[1]; for (i = 0; i < HPAV_BEACON_BTO_NB; i++) { bto = ctx->bpsd[i+2] - ctx->bpsd[i+1] - ctx->beacon_period_theo_tck; /* Does bto overflowed ? */ if (ABS(bto) >> 15 == 0) ctx->bto[i] = bto; else ctx->bto[i] = HPAV_BEACON_BTO_INVALID; } } /** * Timer event function call. * \param user_data the module context. */ void bsu_aclf_timer_event (void *user_data) { bsu_aclf_t *ctx = (bsu_aclf_t*) user_data; dbg_assert (less_mod2p32 (phy_date (), ctx->bpsd[2])); if (ctx->aclsc) bsu_aclf_ac_compute_beacon_period_start_date (ctx); else bsu_aclf_shift_beacon_period_start_date (ctx); hal_timer_instance_program (ctx->hal_timer, &ctx->timer, ctx->bpsd[1]); } /** * Compute the frequency of the power line using the PRATIC register. * \param ctx the module context. * * It shall read the PRATIC register twice with a gap of BSU_ACLF_ZC_50. This * function shall update the data in the object. * * \warn If the medium is a coaxial cable, the 50Hz will be chosen. */ void bsu_aclf_acl_frequency_detection (bsu_aclf_t *ctx) { uint diff_zc_tck; u32 now = phy_date (); u32 zero_cross_new_date, zero_cross_date, wait_until_date; /* Get the last zero cross date from hardware. */ zero_cross_date = zero_cross_new_date = phy_clock_get_zero_cross_captured_date (ctx->phy); /* Protection for maximus. */ if (less_mod2p32 (now, phy_date ())) { /* Catch the first zero cross. * If the frequency does not change the expiration should stop * the loop. */ wait_until_date = phy_date () + MAC_MS_TO_TCK (50); while (lesseq_mod2p32 (phy_date (), wait_until_date) && zero_cross_new_date == zero_cross_date) zero_cross_new_date = phy_clock_get_zero_cross_captured_date (ctx->phy); } /* Compute the beacon period from the zero crossing. * Beacon period is twice the zero crossing. Zero crossing is only * store by hardware on a rising edge of the PowerLine cycle.*/ diff_zc_tck = zero_cross_new_date - zero_cross_date; ctx->beacon_period_tck = 2 * diff_zc_tck; if (diff_zc_tck == 0 || ctx->beacon_period_tck >= BSU_ACLF_BP_55HZ_TCK) BSU_ACLF_SET_FREQUENCY (50); else BSU_ACLF_SET_FREQUENCY (60); ctx->zero_cross_last_date = zero_cross_new_date; bsu_aclf_truncate_beacon_period (ctx); } /** * Clear all data in BSU ACLF. * \param ctx the module context. */ static void bsu_aclf_clear (bsu_aclf_t *ctx) { uint i; for (i = 0; i < COUNT (ctx->bpsd); i++) ctx->bpsd[i] = 0; ctx->beacon_period_tck = ctx->beacon_period_theo_tck; for (i = 0; i < COUNT (ctx->bto); i++) ctx->bto[i] = 0; ctx->zero_cross_last_date = 0; ctx->aclsc = false; } bsu_aclf_t* bsu_aclf_init (phy_t *phy, mac_config_t *mac_config, hal_timer_t *timer) { bsu_aclf_t *ctx = &bsu_aclf_global; dbg_assert (phy); dbg_assert (mac_config); memset (ctx, 0, sizeof (bsu_aclf_t)); ctx->phy = phy; ctx->mac_config = mac_config; ctx->hal_timer = timer; hal_timer_instance_init (timer, &ctx->timer, ctx, bsu_aclf_timer_event); return ctx; } void bsu_aclf_uninit (bsu_aclf_t *ctx) { dbg_assert (ctx); bsu_aclf_clear (ctx); } void bsu_aclf_compute_beacon_period_start_date (bsu_aclf_t *ctx, const u32 bts_ntb, const s16 bto[HPAV_BEACON_BTO_NB], const u32 bpsto, u32 ntb_offset_tck) { uint i; dbg_assert (ctx); u32 bts_date = bts_ntb - ntb_offset_tck - bpsto; ctx->bpsd[0] = bts_date; for (i = 0; i < BSU_ACLF_BPSD_NB - 1; i++) { if (i < HPAV_BEACON_BTO_NB) { ctx->bto[i] = bto[i]; /* BTO is valid use it to compute. */ if (bto[i] != HPAV_BEACON_BTO_INVALID) ctx->bpsd[i+1] = ctx->bpsd[i] + ctx->beacon_period_theo_tck + bto[i]; /* BTO is not valid, take the difference from the previous beacon * period. */ else ctx->bpsd[i+1] += ctx->bpsd[i] - ctx->bpsd[i-1]; } else ctx->bpsd[i+1] += ctx->bpsd[i] - ctx->bpsd[i-1]; } ctx->beacon_period_tck = ctx->bpsd[1] - ctx->bpsd[0]; } void bsu_aclf_bto (bsu_aclf_t *ctx, s16 btos[], uint nb) { dbg_assert (ctx); dbg_assert (btos); dbg_assert (nb <= HPAV_BEACON_BTO_NB); uint i; for (i = 0; i < nb; i++) btos[i] = ctx->bto[i]; } void bsu_aclf_aclsc (bsu_aclf_t *ctx, bool aclsc) { dbg_assert (ctx); ctx->aclsc = aclsc; } void bsu_aclf_activate (bsu_aclf_t *ctx, bool status) { dbg_assert (ctx); dbg_assert (ctx->activate != status); ctx->activate = status; if (status) { bsu_aclf_acl_frequency_detection (ctx); bsu_aclf_ac_compute_beacon_period_start_date (ctx); hal_timer_instance_program (ctx->hal_timer, &ctx->timer, ctx->bpsd[1]); } else { hal_timer_instance_cancel (ctx->hal_timer, &ctx->timer); bsu_aclf_clear (ctx); } }