Changes ported from modules, to support multiple tmp101 sensors.
[dtplug] / core / system.c
1 /****************************************************************************
2  *   core/system.c
3  *
4  * All low-level functions for clocks configuration and switch, system
5  *  power-up, reset, and power-down.
6  *
7  * Copyright 2012 Nathael Pajani <nathael.pajani@ed3l.fr>
8  *
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation, either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  *
23  *************************************************************************** */
25 /*
26  * This file holds some system wide initialisation functions and clock or sleep
27  *   related functions.
28  */
30 #include <stdint.h>
32 #include "core/lpc_regs_17xx.h"
33 #include "core/lpc_core_cm3.h"
34 #include "core/system.h"
35 #include "core/pio.h"
38 /* Private defines */
39 #define LPC_XTAL         (12000000UL)  /* Oscillator frequency : 12MHz on DTPlug board */
40 #define LPC_SYS_OSC_CLK  (LPC_XTAL)    /* Main oscillator frequency */
41 #define LPC_IRC_OSC_CLK  (4000000UL)  /* Internal RC oscillator frequency : 4MHz */
44 /***************************************************************************** */
45 /*                     Global data                                             */
46 /***************************************************************************** */
47 struct lpc_desc_private {
48         uint32_t main_clock;
49         uint32_t brown_out_detection_enabled;
50 };
51 static struct lpc_desc_private lpc_private = {
52         .main_clock = LPC_IRC_OSC_CLK,
53         .brown_out_detection_enabled = 0,
54 };
56 /***************************************************************************** */
57 /*                     Required system inits                                  */
58 /***************************************************************************** */
59 /* Set up number of CPU clocks for flash access (see chapter 5.4 of LPC17xx User
60  *  manual (UM10360.pdf))
61  * freq_sel : code for CPU freq, as defined in system.h header file :
62  * A clock frequency is defined as the integer value in MHz shifted by 3 and or'ed
63  * with the value to be programmed in the flash config register for the flash access
64  * time at the given frequency.
65  */
66 static void flash_accelerator_config(uint32_t freq_sel)
67 {
68         struct lpc_flash_control* fcfg = LPC_FLASH_CONTROL;
69         fcfg->flash_cfg &= ~(LPC_FLASH_CFG_MASK);
70         fcfg->flash_cfg |= ((freq_sel & 0x07) << LPC_FLASH_CFG_SHIFT);
71 }
74 /* Stop the watchdog
75  * Note that after reset the Watchdog is not running.
76  */
77 void stop_watchdog(void)
78 {
79     struct lpc_watchdog* wdt = LPC_WDT;
80         /* Stop watchdog */
81     wdt->mode = LPC_WDT_DISABLE;
82     wdt->feed_seqence = 0xAA;
83     wdt->feed_seqence = 0x55;
84 }
86 /* Configure the brown-out detection */
87 void system_brown_out_detection_config(uint32_t level)
88 {
89         struct lpc_sys_control* ctrl = LPC_SYS_CONTROL;
91         if (level == 0) {
92                 /* Disable Brown_Out detection and clear flags */
93                 ctrl->power_mode_ctrl |= (LPC_BOD_FULL_DISABLE | LPC_CLEAR_ALL_SLEEP_FLAGS);
94                 /* Disable Brown-Out Detection, power it down */
95                 lpc_private.brown_out_detection_enabled = 0;
96         } else {
97                 /* Enable Brown-Out Detection. */
98                 /* FIXME */
99                 lpc_private.brown_out_detection_enabled = 1;
100                 /* Configure Brown-Out Detection */
101         }
104 /***************************************************************************** */
105 /*            Peripheral Power and Clocks                                      */
106 /***************************************************************************** */
107 /* Change reset power state to our default, removing power from unused (all but
108  *  the RTC and GPIO) interfaces
109  */
110 void system_set_default_power_state(void)
112         struct lpc_sys_control* ctrl = LPC_SYS_CONTROL;
113         /* Set lpc_sys_control->periph_power_ctrl (PCONP) to apropriate bitmask */
114         ctrl->periph_power_ctrl = (LPC_RTC_POWER_ON | LPC_GPIO_POWER_ON);
117 /* Set the Peripheral Clock divider in PCLKSEL0 and PCLKSEL1
118  * The default value at reset is LPC_PCLK_CCLK_QUARTER which divides the CCLK by 4.
119  */
120 void set_subsystem_clk_divider(uint32_t subsystem, uint32_t clk_div)
122         struct lpc_sys_control* sys_ctrl = LPC_SYS_CONTROL;
124         sys_ctrl->pclk_sel[ ((subsystem & 0x80) >> 7) ] = ((clk_div & 0x03) << (subsystem & 0x1E));
128 /* Power ON or OFF a subsystem
129  * Power OFF if 'on_off' is 0, turn ON otherwise
130  * 'power_bit' uses defines from core/lpc_regs_17xx.h : LPC_xxxx_POWER_ON
131  */
132 void subsystem_power(uint32_t power_bit, uint32_t on_off)
134         struct lpc_sys_control* sys_ctrl = LPC_SYS_CONTROL;
135         if (on_off != 0) {
136                 sys_ctrl->periph_power_ctrl |= power_bit; /* Power on */
137         } else {
138                 sys_ctrl->periph_power_ctrl &= ~(power_bit);
139         }
141 uint8_t subsystem_powered(uint32_t power_bit)
143         struct lpc_sys_control* sys_ctrl = LPC_SYS_CONTROL;
144         return (sys_ctrl->periph_power_ctrl & power_bit);
148 /***************************************************************************** */
149 /* Sleep and power-down modes */
150 /* There are four sleep / power down modes in the LPC176x micro-controller
151  *   Sleep - Deep Sleep - Power Down - Deep Power Down
152  *
153  * Note : Refer to chapter 4.8 'Power control' page 58 and following ones in UM10360 User manual
154  */
156 /* Enter deep sleep. */
157 void enter_deep_sleep(void)
159         struct lpc_sys_control* sys_ctrl = LPC_SYS_CONTROL;
160         struct syst_ctrl_block_regs* sys_ctrl_block = LPC_SCB;
161         uint32_t tmp = sys_ctrl->power_mode_ctrl;
162         /* Configure for sleep */
163         tmp &= LPC_BOD_FULL_DISABLE; /* Remove all bits but those for BOD, preserve flags */
164         tmp |= LPC_SLEEP_ENTRY;
165         sys_ctrl->power_mode_ctrl = tmp;
166         /* Configure for deep sleep */
167         sys_ctrl_block->scr |= SCB_SCR_SLEEPDEEP; /* FIXME : do we set SEVONPEND and/or SLEEPONEXIT */
168         /* Enter deep sleep */
169         wfe();
171         /* We will return in execution mode here */
172         /* FIXME : Wait for OSCSTAT in SCS and Restore PLLs */
175 /* FIXME : Add support for other sleep / power-down modes */
178 /***************************************************************************** */
179 /*                      System Clock                                           */
180 /***************************************************************************** */
181 static void propagate_main_clock(void);
183 /* Main clock config : set up the system clock
184  * We use internal RC oscilator as sys_pllclkin if PLL is ussed
185  * Note that during PLL lock wait we are running on internal RC
186  * Note for M and N calculation : FCCO must be between 275MHz and 550MHz
187  *                                M ranges from 6 to 512
188  *                                N ranges from 1 to 32
189  */
190 void clock_config(uint32_t freq_sel)
192         struct lpc_sys_control* sys_ctrl = LPC_SYS_CONTROL;
193         uint32_t M = 0; /* M = (FCCO × N) / (2 × FIN) */
194         uint32_t N = 0; /* N = (2 × M × FIN) / FCCO */
195         uint32_t div = 0; /* divider of PLL0 output to provide desired frequency */
197         /* Select dividers M, N, and PLL0 output divider (div) */
198         /* Note : We will generate USB clock using PLL1 */
199         switch (freq_sel) {
200                 case FREQ_SEL_48MHz:
201                         lpc_private.main_clock = (48*1000*1000);
202                         M = 12;  N = 1;  div = 6;
203                         break;
204                 case FREQ_SEL_50MHz:
205                         lpc_private.main_clock = (50*1000*1000);
206                         M = 25;  N = 2;  div = 6;
207                         break;
208                 case FREQ_SEL_60MHz:
209                         lpc_private.main_clock = (60*1000*1000);
210                         M = 15;  N = 1;  div = 6;
211                         break;
212                 case FREQ_SEL_72MHz:
213                         lpc_private.main_clock = (72*1000*1000);
214                         M = 12;  N = 1;  div = 4;
215                         break;
216                 case FREQ_SEL_100MHz:
217                 default:
218                         lpc_private.main_clock = (100*1000*1000);
219                         M = 25;  N = 2;  div = 3;
220                         break;
221         }
222         /* Configure PLL 0 for CPU */
223         /* Note :  ALL STEPS ARE REQUIRED ! */
224         /* First of all, turn off interrupts, nothing should happen between the two writes of the
225            feed sequence (writting of 0xAA and 0x55 to pll0_feed) */
226         lpc_disable_irq();
227         /* Disable PLL if requiered */
228         if ((sys_ctrl->pll0_status & (0x03 << 24)) != 0) {
229                 /* Disconnect PLL first */
230                 sys_ctrl->pll0_control &= ~(LPC_PLL_CTRL_PLL_CONNECT);
231                 sys_ctrl->pll0_feed = 0xAA;
232                 sys_ctrl->pll0_feed = 0x55;
233                 /* Then disable PLL */
234                 sys_ctrl->pll0_control = 0;
235                 sys_ctrl->pll0_feed = 0xAA;
236                 sys_ctrl->pll0_feed = 0x55;
237         }
238         /* PLL now off. Set clock div to 1 to get max freq */
239         sys_ctrl->cpu_clk_cfg = 0; /* Set div to 1 : possible because PLL is not used */
240         /* Make sure that external crystal is enabled and ready */
241         sys_ctrl->sys_osc_ctrl_and_status = (LPC_SYS_CTRL_OSC_EN | LPC_SYS_CTRL_OSC_RANGE_UNDER_20MHZ);
242         while ( ! (sys_ctrl->sys_osc_ctrl_and_status & LPC_SYS_CTRL_OSC_STAT) );
243         /* Use external crystal as source */
244         sys_ctrl->sys_clk_src_sel = LPC_CLK_PLL0_SRC_XTAL;
245         /* Setup PLL */
246         sys_ctrl->pll0_config = (((M - 1) & 0x7FFF) | (((N - 1) & 0x3F) << 16));
247         sys_ctrl->pll0_feed = 0xAA;
248         sys_ctrl->pll0_feed = 0x55;
249         /* Enable PLL */
250         sys_ctrl->pll0_control = LPC_PLL_CTRL_PLL_ENABLE;
251         sys_ctrl->pll0_feed = 0xAA;
252         sys_ctrl->pll0_feed = 0x55;
253         /* Set divisor for CPU Clock before connecting PLL (must NOT stay as 1) */
254         sys_ctrl->cpu_clk_cfg = ((div - 1) & 0xFF);
255         /* Set flash accelerator CPU cycles for flash access with new speed */
256         flash_accelerator_config(freq_sel);
257         /* Wait for PLL lock (note : interrupts are disabled) */
258         while ( ! (sys_ctrl->pll0_status & (0x01 << 26)) );
259         /* Connect PLL */
260         sys_ctrl->pll0_control = (LPC_PLL_CTRL_PLL_ENABLE | LPC_PLL_CTRL_PLL_CONNECT);
261         sys_ctrl->pll0_feed = 0xAA;
262         sys_ctrl->pll0_feed = 0x55;
263         /* Propagate Main clock change before enabling interrupts back */
264         propagate_main_clock();
265         /* Turn interrupts on once again*/
266         lpc_enable_irq();
269 uint32_t get_main_clock(void)
271         return lpc_private.main_clock;
274 /***************************************************************************** */
275 /*                     Peripheral Clocks                                       */
276 /***************************************************************************** */
277 void Dummy_Clk_Updater(void) {
278         do { } while (0);
281 void uart_clk_update(void) __attribute__ ((weak, alias ("Dummy_Clk_Updater")));
282 void i2c_clock_update(void) __attribute__ ((weak, alias ("Dummy_Clk_Updater")));
283 void ssp_clk_update(void) __attribute__ ((weak, alias ("Dummy_Clk_Updater")));
284 void adc_clk_update(void) __attribute__ ((weak, alias ("Dummy_Clk_Updater")));
286 static void propagate_main_clock(void)
288         uart_clk_update();
289         i2c_clock_update();
290         ssp_clk_update();
291         adc_clk_update();
295 /***************************************************************************** */
296 /*                    CLK Out                                                  */
297 /***************************************************************************** */
298 /* This is mainly a debug feature, but can be used to provide a clock to an
299  * external peripheral */
300 /* Note that PIO0_12 is the only pin possible for CLK_Out, and is multiplexed
301  * with ISP mode selection on reset.
302  * The pin must be enabled using a pio table passed to the set_pins() function or
303  *   a call to config_pio() with the appropriate pin (LPC_CLKOUT_PIO_1_27).
304  */
306 /* Debug : the clkout is only connected to a testpoint (P15) */
307 void clkout_on(uint32_t src, uint32_t div)
309         struct lpc_sys_control* sys_ctrl = LPC_SYS_CONTROL;
311         if (src > LPC_CLKOUT_SRC_MAX) {
312                 src = LPC_CLKOUT_SRC_CPU;
313         }
314         /* Configure :             SRC       |       DIV           |  Enable   */
315         sys_ctrl->clkout_cfg = ((src & 0x07) | LPC_CLKOUT_DIV(div) | LPC_CLKOUT_ENABLE);
317 void clkout_off()
319         struct lpc_sys_control* sys_ctrl = LPC_SYS_CONTROL;
320         sys_ctrl->clkout_cfg = 0;
326 /***************************************************************************** */
327 /*                    Default sleep function                                   */
328 /***************************************************************************** */
329 /* Note that if the systick core functions are used these will be overridden */
331 /* This "msleep" is a simple wait routine which is close to a millisecond sleep
332  * whith a CPU Clock of 24MHz, but has no exact relation to time.
333  * It is highly dependent to CPU clock speed anyway.
334  * Note : This is an active sleep !
335  */
336 static void def_msleep(uint32_t ms)
338         volatile uint32_t dec = ms * 2667;
339         while (dec--);
342 /* Something that's not too far from a microsecond sleep at 24MHz CPU Clock
343  * Note : This is an active sleep !
344  */
345 static inline void def_usleep(uint32_t us)
347         volatile uint32_t dec = us * 2;
348         while (dec--);
351 void msleep(uint32_t ms) __attribute__ ((weak, alias ("def_msleep")));
352 void usleep(uint32_t us) __attribute__ ((weak, alias ("def_usleep")));