/*
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 *
 * This file is part of PortaPack.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#include "rffc507x.hpp"

#include <array>

#include "utility.hpp"

#include "hackrf_hal.hpp"
#include "hackrf_gpio.hpp"
using namespace hackrf::one;

#include "hal.h"

namespace rffc507x {

/* Empirical tests indicate no minimum reset pulse width, but the speed
 * of the processor and GPIO probably produce at least 20ns pulse width.
 */
constexpr float seconds_during_reset = 1.0e-6;
constexpr halrtcnt_t ticks_during_reset = (base_m4_clk_f * seconds_during_reset + 1);

/* Empirical testing indicates >3.5us delay required after reset, before
 * registers can be reliably written. Make it 5us, just for fun. Tests were
 * conducted at high temperatures (with a hair dryer) increased room
 * temperature minimum delay of 2.9us to the requirement above.
 */
constexpr float seconds_after_reset = 5.0e-6;
constexpr halrtcnt_t ticks_after_reset = (base_m4_clk_f * seconds_after_reset + 1);

constexpr auto reference_frequency = rffc5072_reference_f;

namespace vco {

constexpr rf::FrequencyRange range {
	.min = 2700000000U,
	.max = 5400000000U,
};

} /* namespace vco */

namespace lo {

constexpr size_t divider_log2_min = 0;
constexpr size_t divider_log2_max = 5;

constexpr size_t divider_min = 1U << divider_log2_min;
constexpr size_t divider_max = 1U << divider_log2_max;

constexpr rf::FrequencyRange range {
	.min = vco::range.min / divider_max,
	.max = vco::range.max / divider_min,
};

size_t divider_log2(const rf::Frequency lo_frequency) {
	/* TODO: Error */
	/*
	if( lo::range.out_of_range(lo_frequency) ) {
		return;
	}
	*/
	/* Compute LO divider. */
	auto lo_divider_log2 = lo::divider_log2_min;
	auto vco_frequency = lo_frequency;
	while( vco::range.below_range(vco_frequency) ) {
		vco_frequency <<= 1;
		lo_divider_log2 += 1;
	}

	return lo_divider_log2;
}

} /* namespace lo */

namespace prescaler {

constexpr rf::Frequency max_frequency = 1600000000U;

constexpr size_t divider_log2_min = 1;
constexpr size_t divider_log2_max = 2;

constexpr size_t divider_min = 1U << divider_log2_min;
constexpr size_t divider_max = 1U << divider_log2_max;

constexpr size_t divider_log2(const rf::Frequency vco_frequency) {
	return (vco_frequency > (prescaler::divider_min * prescaler::max_frequency))
		? prescaler::divider_log2_max
		: prescaler::divider_log2_min
		;
}

} /* namespace prescaler */

struct SynthConfig {
	const size_t lo_divider_log2;
	const size_t prescaler_divider_log2;
	const uint64_t n_divider_q24;

	static SynthConfig calculate(
		const rf::Frequency lo_frequency
	) {
		/* RFFC507x frequency synthesizer is is accurate to about 2ppb (two parts
		 * per BILLION). There's not much point to worrying about rounding and
		 * tuning error, when it amounts to 8Hz at 5GHz!
		 */
		const size_t lo_divider_log2 = lo::divider_log2(lo_frequency);
		const size_t lo_divider = 1U << lo_divider_log2;

		const rf::Frequency vco_frequency = lo_frequency * lo_divider;

		const size_t prescaler_divider_log2 = prescaler::divider_log2(vco_frequency);

		const uint64_t prescaled_lo_q24 = vco_frequency << (24 - prescaler_divider_log2);
		const uint64_t n_divider_q24 = prescaled_lo_q24 / reference_frequency;

		return {
			lo_divider_log2,
			prescaler_divider_log2,
			n_divider_q24,
		};
	}
};

/* Readback values, RFFC5072 rev A:
 * 0000: 0x8a01 => dev_id=1000101000000 mrev_id=001
 * 0001: 0x3f7c => lock=0 ct_cal=0111111 cp_cal=011111 ctfail=0 0
 * 0010: 0x806f => v0_cal=10000000 v1_cal=01101111
 * 0011: 0x0000 => rsm_state=00000 f_errflag=00
 * 0100: 0x0000 => vco_count_l=0
 * 0101: 0x0000 => vco_count_h=0
 * 0110: 0xc000 => cal_fbi=1 cal_fbq=1
 * 0111: 0x0000 => vco_sel=0 vco_tc_curve=0
 */

void RFFC507x::init() {
	gpio_rffc5072_resetx.set();
	gpio_rffc5072_resetx.output();
	reset();

	_bus.init();

	_dirty.set();
	flush();
}

void RFFC507x::reset() {
	/* TODO: Is RESETB pin ignored if sdi_ctrl.sipin=1? Programming guide
	 * description of sdi_ctrl.sipin suggests the pin is not ignored.
	 */
	gpio_rffc5072_resetx.clear();
	halPolledDelay(ticks_during_reset);
	gpio_rffc5072_resetx.set();
	halPolledDelay(ticks_after_reset);
}

void RFFC507x::flush() {
	if( _dirty ) {
		for(size_t i=0; i<_map.w.size(); i++) {
			if( _dirty[i] ) {
				write(i, _map.w[i]);
			}
		}
		_dirty.clear();
	}
}

inline void RFFC507x::write(const address_t reg_num, const spi::reg_t value) {
	_bus.write(reg_num, value);
}

inline spi::reg_t RFFC507x::read(const address_t reg_num) {
	return _bus.read(reg_num);
}

inline void RFFC507x::write(const Register reg, const spi::reg_t value) {
	write(toUType(reg), value);
}

inline spi::reg_t RFFC507x::read(const Register reg) {
	return read(toUType(reg));
}

inline void RFFC507x::flush_one(const Register reg) {
	const auto reg_num = toUType(reg);
	write(reg_num, _map.w[reg_num]);
	_dirty.clear(reg_num);
}

void RFFC507x::enable() {
	_map.r.sdi_ctrl.enbl = 1;
	flush_one(Register::SDI_CTRL);

	/* TODO: Reset PLLCPL after CT_CAL? */

	/* TODO: After device is enabled and CT_cal is complete and VCO > 3.2GHz,
	 * change prescaler divider to 2, update synthesizer ratio, change
	 * lf.pllcpl from 3 to 2.
	 */
}

void RFFC507x::disable() {
	_map.r.sdi_ctrl.enbl = 0;
	flush_one(Register::SDI_CTRL);
}

void RFFC507x::set_mixer_current(const uint8_t value) {
	/* MIX IDD = 0b000 appears to turn the mixer completely off */
	/* TODO: Adjust mixer current. Graphs in datasheet suggest:
	 * MIX_IDD=1 has lowest noise figure (10.1dB vs 13dB @ MIX_IDD=7).
	 * MIX_IDD=5 has highest IP3 (24dBm vs 10.3dBm @ MIX_IDD=1).
	 * MIX_IDD=5 has highest P1dB (11.8dBm vs 1.5dBm @ MIX_IDD=1).
	 * Mixer input impedance ~85 Ohms at MIX_IDD=4.
	 * Mixer input impedance inversely proportional to MIX_IDD.
	 * Balun balanced (mixer) side is 100 Ohms. Perhaps reduce MIX_IDD
	 * a bit to get 100 Ohms from mixer.
	 */
	_map.r.mix_cont.p1mixidd = value;
	_map.r.mix_cont.p2mixidd = value;
	flush_one(Register::MIX_CONT);
}

void RFFC507x::set_frequency(const rf::Frequency lo_frequency) {
	const SynthConfig synth_config = SynthConfig::calculate(lo_frequency);

	/* Boost charge pump leakage if VCO frequency > 3.2GHz, indicated by
	 * prescaler divider set to 4 (log2=2) instead of 2 (log2=1).
	 */
	if( synth_config.prescaler_divider_log2 == 2 ) {
		_map.r.lf.pllcpl = 3;
	} else {
		_map.r.lf.pllcpl = 2;
	}
	flush_one(Register::LF);

	_map.r.p2_freq1.p2n = synth_config.n_divider_q24 >> 24;
	_map.r.p2_freq1.p2lodiv = synth_config.lo_divider_log2;
	_map.r.p2_freq1.p2presc = synth_config.prescaler_divider_log2;
	_map.r.p2_freq2.p2nmsb = (synth_config.n_divider_q24 >> 8) & 0xffff;
	_map.r.p2_freq3.p2nlsb = synth_config.n_divider_q24 & 0xff;
	_dirty[Register::P2_FREQ1] = 1;
	_dirty[Register::P2_FREQ2] = 1;
	_dirty[Register::P2_FREQ3] = 1;
	flush();
}

spi::reg_t RFFC507x::readback(const Readback readback) {
	/* TODO: This clobbers the rest of the DEV_CTRL register
	 * Time to implement bitfields for registers.
	 */
	_map.r.dev_ctrl.readsel = toUType(readback);
	flush_one(Register::DEV_CTRL);

	return read(Register::READBACK);
}

RegisterMap RFFC507x::registers() {
	return _map;
}

#if 0
/* Test of RFFC507x reset over temperature */
while(true) {
	first_if.write(rffc507x::Register::P1_FREQ2, 0xAAAA);
	first_if.reset();
	const auto after_reset = first_if.read(rffc507x::Register::P1_FREQ2);
	if( after_reset != 0x6276 ) {
		led_usb.off();
		led_tx.on();
		chThdSleepMilliseconds(100);
	} else {
		led_usb.on();
		led_tx.off();
	}
}
#endif

} /* namespace rffc507x */
