/* * 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 "portapack.hpp" #include "portapack_hal.hpp" #include "portapack_dma.hpp" #include "portapack_cpld_data.hpp" #include "portapack_persistent_memory.hpp" #include "hackrf_hal.hpp" #include "hackrf_gpio.hpp" using namespace hackrf::one; #include "clock_manager.hpp" #include "event_m0.hpp" #include "backlight.hpp" #include "touch_adc.hpp" #include "audio.hpp" #include "wm8731.hpp" using wolfson::wm8731::WM8731; #include "ak4951.hpp" using asahi_kasei::ak4951::AK4951; #include "cpld_update.hpp" #include "optional.hpp" namespace portapack { portapack::IO io { portapack::gpio_dir, portapack::gpio_lcd_rdx, portapack::gpio_lcd_wrx, portapack::gpio_io_stbx, portapack::gpio_addr, portapack::gpio_lcd_te, portapack::gpio_unused, }; portapack::BacklightCAT4004 backlight_cat4004; portapack::BacklightOnOff backlight_on_off; lcd::ILI9341 display; I2C i2c0(&I2CD0); SPI ssp1(&SPID2); si5351::Si5351 clock_generator { i2c0, hackrf::one::si5351_i2c_address }; ClockManager clock_manager { i2c0, clock_generator }; WM8731 audio_codec_wm8731 { i2c0, 0x1a }; AK4951 audio_codec_ak4951 { i2c0, 0x12 }; ReceiverModel receiver_model; TransmitterModel transmitter_model; TemperatureLogger temperature_logger; bool antenna_bias { false }; uint8_t bl_tick_counter { 0 }; void set_antenna_bias(const bool v) { antenna_bias = v; } bool get_antenna_bias() { return antenna_bias; } static constexpr uint32_t systick_count(const uint32_t clock_source_f) { return clock_source_f / CH_FREQUENCY; } static constexpr uint32_t systick_load(const uint32_t clock_source_f) { return systick_count(clock_source_f) - 1; } constexpr uint32_t i2c0_bus_f = 400000; constexpr uint32_t i2c0_high_period_ns = 900; typedef struct { uint32_t clock_f; uint32_t systick_count; uint32_t idivb; uint32_t idivc; } clock_config_t; static constexpr uint32_t idiv_config(const cgu::CLK_SEL clk_sel, const uint32_t idiv) { return cgu::IDIV_CTRL { 0, idiv-1, 1, clk_sel }; } constexpr clock_config_t clock_config_irc { 12000000, systick_load(12000000), idiv_config(cgu::CLK_SEL::IRC, 1), idiv_config(cgu::CLK_SEL::IRC, 1), }; constexpr clock_config_t clock_config_pll1_boot { 96000000, systick_load(96000000), idiv_config(cgu::CLK_SEL::PLL1, 9), idiv_config(cgu::CLK_SEL::PLL1, 3), }; constexpr clock_config_t clock_config_pll1_step { 100000000, systick_load(100000000), idiv_config(cgu::CLK_SEL::PLL1, 1), idiv_config(cgu::CLK_SEL::PLL1, 1), }; constexpr clock_config_t clock_config_pll1 { 200000000, systick_load(200000000), idiv_config(cgu::CLK_SEL::PLL1, 2), idiv_config(cgu::CLK_SEL::PLL1, 1), }; constexpr I2CClockConfig i2c_clock_config_400k_boot_clock { .clock_source_f = clock_config_pll1_boot.clock_f, .bus_f = i2c0_bus_f, .high_period_ns = i2c0_high_period_ns, }; constexpr I2CClockConfig i2c_clock_config_400k_fast_clock { .clock_source_f = clock_config_pll1.clock_f, .bus_f = i2c0_bus_f, .high_period_ns = i2c0_high_period_ns, }; constexpr I2CConfig i2c_config_boot_clock { .high_count = i2c_clock_config_400k_boot_clock.i2c_high_count(), .low_count = i2c_clock_config_400k_boot_clock.i2c_low_count(), }; constexpr I2CConfig i2c_config_fast_clock { .high_count = i2c_clock_config_400k_fast_clock.i2c_high_count(), .low_count = i2c_clock_config_400k_fast_clock.i2c_low_count(), }; enum class PortaPackModel { R1_20150901, R2_20170522, }; static PortaPackModel portapack_model() { static Optional<PortaPackModel> model; if( !model.is_valid() ) { if( audio_codec_wm8731.detected() ) { model = PortaPackModel::R1_20150901; } else { model = PortaPackModel::R2_20170522; } } return model.value(); } static audio::Codec* portapack_audio_codec() { return (portapack_model() == PortaPackModel::R2_20170522) ? static_cast<audio::Codec*>(&audio_codec_ak4951) : static_cast<audio::Codec*>(&audio_codec_wm8731) ; } static const portapack::cpld::Config& portapack_cpld_config() { return (portapack_model() == PortaPackModel::R2_20170522) ? portapack::cpld::rev_20170522::config : portapack::cpld::rev_20150901::config ; } Backlight* backlight() { return (portapack_model() == PortaPackModel::R2_20170522) ? static_cast<portapack::Backlight*>(&backlight_cat4004) : static_cast<portapack::Backlight*>(&backlight_on_off); } #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) static LPC_CGU_BASE_CLK_Type* const base_clocks_idivc[] = { &LPC_CGU->BASE_PERIPH_CLK, &LPC_CGU->BASE_M4_CLK, &LPC_CGU->BASE_APB1_CLK, &LPC_CGU->BASE_APB3_CLK, &LPC_CGU->BASE_SDIO_CLK, &LPC_CGU->BASE_SSP1_CLK, }; static void set_idivc_base_clocks(const cgu::CLK_SEL clock_source) { for(uint32_t i=0; i<ARRAY_SIZE(base_clocks_idivc); i++) { base_clocks_idivc[i]->AUTOBLOCK = 1; base_clocks_idivc[i]->CLK_SEL = toUType(clock_source); } } static void set_clock_config(const clock_config_t& config) { LPC_CGU->IDIVB_CTRL.word = config.idivb; LPC_CGU->IDIVC_CTRL.word = config.idivc; systick_adjust_period(config.systick_count); halLPCSetSystemClock(config.clock_f); } static void shutdown_base() { i2c0.stop(); set_clock_config(clock_config_irc); cgu::pll1::disable(); set_idivc_base_clocks(cgu::CLK_SEL::IRC); cgu::pll1::ctrl({ .pd = 1, .bypass = 0, .fbsel = 0, .direct = 1, .psel = 0, .autoblock = 1, .nsel = 0, .msel = 23, .clk_sel = cgu::CLK_SEL::IRC, }); cgu::pll1::enable(); while( !cgu::pll1::is_locked() ); set_clock_config(clock_config_pll1_boot); i2c0.start(i2c_config_boot_clock); clock_manager.shutdown(); } /* Clock scheme after exiting bootloader in SPIFI mode: * * XTAL_OSC = powered down * * PLL0USB = powered down * PLL0AUDIO = powered down * PLL1 = IRC * 24 = 288 MHz * * IDIVA = IRC / 1 = 12 MHz * IDIVB = PLL1 / 9 = 32 MHz * IDIVC = PLL1 / 3 = 96 MHz * IDIVD = IRC / 1 = 12 MHz * IDIVE = IRC / 1 = 12 MHz * * BASE_USB0_CLK = PLL0USB * BASE_PERIPH_CLK = IRC * BASE_M4_CLK = IDIVC (96 MHz) * BASE_SPIFI_CLK = IDIVB (32 MHZ) * * everything else = IRC */ /* Clock scheme during PortaPack operation: * * XTAL_OSC = powered down * * PLL0USB = powered down * PLL0AUDIO = GP_CLKIN, Fcco=491.52 MHz, Fout=12.288 MHz * PLL1 = GP_CLKIN * 10 = 200 MHz * * IDIVA = IRC / 1 = 12 MHz * IDIVB = PLL1 / 2 = 100 MHz * IDIVC = PLL1 / 1 = 200 MHz * IDIVD = PLL0AUDIO / N (where N is varied depending on decimation factor) * IDIVE = IRC / 1 = 12 MHz * * BASE_USB0_CLK = PLL0USB * BASE_PERIPH_CLK = IRC * BASE_M4_CLK = IDIVC (200 MHz) * BASE_SPIFI_CLK = IDIVB (100 MHZ) * BASE_AUDIO_CLK = IDIVD * * everything else = IRC */ bool init() { set_idivc_base_clocks(cgu::CLK_SEL::IDIVC); i2c0.start(i2c_config_boot_clock); if( !portapack::cpld::update_if_necessary(portapack_cpld_config()) ) { shutdown_base(); return false; } if( !hackrf::cpld::load_sram() ) { chSysHalt(); } configure_pins_portapack(); portapack::io.init(); clock_manager.init_clock_generator(); i2c0.stop(); set_clock_config(clock_config_irc); cgu::pll1::disable(); /* Incantation from LPC43xx UM10503 section 12.2.1.1, to bring the M4 * core clock speed to the 110 - 204MHz range. */ /* Step into the 90-110MHz M4 clock range */ /* Fclkin = 40M * /N=2 = 20M = PFDin * Fcco = PFDin * (M=10) = 200M * Fclk = Fcco / (2*(P=1)) = 100M */ cgu::pll1::ctrl({ .pd = 1, .bypass = 0, .fbsel = 0, .direct = 0, .psel = 0, .autoblock = 1, .nsel = 1, .msel = 9, .clk_sel = cgu::CLK_SEL::GP_CLKIN, }); cgu::pll1::enable(); while( !cgu::pll1::is_locked() ); set_clock_config(clock_config_pll1_step); /* Delay >50us at 90-110MHz clock speed */ volatile uint32_t delay = 1400; while(delay--); set_clock_config(clock_config_pll1); /* Remove /2P divider from PLL1 output to achieve full speed */ cgu::pll1::direct(); i2c0.start(i2c_config_fast_clock); clock_manager.set_reference_ppb(persistent_memory::correction_ppb()); audio::init(portapack_audio_codec()); clock_manager.enable_first_if_clock(); clock_manager.enable_second_if_clock(); clock_manager.enable_codec_clocks(); radio::init(); touch::adc::init(); LPC_CREG->DMAMUX = portapack::gpdma_mux; gpdma::controller.enable(); return true; } void shutdown() { gpdma::controller.disable(); backlight()->off(); display.shutdown(); radio::disable(); audio::shutdown(); hackrf::cpld::init_from_eeprom(); shutdown_base(); } } /* namespace portapack */