362 lines
9.3 KiB
C
362 lines
9.3 KiB
C
/*
|
|
* Copyright (c) 2020, Seagate Technology LLC
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT nxp_lpc11u6x_syscon
|
|
|
|
#include <zephyr/devicetree.h>
|
|
#include <zephyr/device.h>
|
|
|
|
#include <zephyr/drivers/clock_control/lpc11u6x_clock_control.h>
|
|
|
|
#include "clock_control_lpc11u6x.h"
|
|
|
|
static void syscon_power_up(struct lpc11u6x_syscon_regs *syscon,
|
|
uint32_t bit, bool enable)
|
|
{
|
|
if (enable) {
|
|
syscon->pd_run_cfg = (syscon->pd_run_cfg & ~bit)
|
|
| LPC11U6X_PDRUNCFG_MASK;
|
|
} else {
|
|
syscon->pd_run_cfg = syscon->pd_run_cfg | bit
|
|
| LPC11U6X_PDRUNCFG_MASK;
|
|
}
|
|
}
|
|
|
|
static void syscon_set_pll_src(struct lpc11u6x_syscon_regs *syscon,
|
|
uint32_t src)
|
|
{
|
|
syscon->sys_pll_clk_sel = src;
|
|
syscon->sys_pll_clk_uen = 0;
|
|
syscon->sys_pll_clk_uen = 1;
|
|
}
|
|
|
|
static void set_flash_access_time(uint32_t nr_cycles)
|
|
{
|
|
uint32_t *reg = (uint32_t *) LPC11U6X_FLASH_TIMING_REG;
|
|
|
|
*reg = (*reg & (~LPC11U6X_FLASH_TIMING_MASK)) | nr_cycles;
|
|
}
|
|
|
|
static void syscon_setup_pll(struct lpc11u6x_syscon_regs *syscon,
|
|
uint32_t msel, uint32_t psel)
|
|
{
|
|
uint32_t val = msel & LPC11U6X_SYS_PLL_CTRL_MSEL_MASK;
|
|
|
|
val |= (psel & LPC11U6X_SYS_PLL_CTRL_PSEL_MASK) <<
|
|
LPC11U6X_SYS_PLL_CTRL_PSEL_SHIFT;
|
|
syscon->sys_pll_ctrl = val;
|
|
}
|
|
|
|
static bool syscon_pll_locked(struct lpc11u6x_syscon_regs *syscon)
|
|
{
|
|
return (syscon->sys_pll_stat & 0x1) != 0;
|
|
}
|
|
|
|
static void syscon_set_main_clock_source(struct lpc11u6x_syscon_regs *syscon,
|
|
uint32_t src)
|
|
{
|
|
syscon->main_clk_sel = src;
|
|
syscon->main_clk_uen = 0;
|
|
syscon->main_clk_uen = 1;
|
|
}
|
|
|
|
static void syscon_ahb_clock_enable(struct lpc11u6x_syscon_regs *syscon,
|
|
uint32_t mask, bool enable)
|
|
{
|
|
if (enable) {
|
|
syscon->sys_ahb_clk_ctrl |= mask;
|
|
} else {
|
|
syscon->sys_ahb_clk_ctrl &= ~mask;
|
|
}
|
|
}
|
|
|
|
static void syscon_peripheral_reset(struct lpc11u6x_syscon_regs *syscon,
|
|
uint32_t mask, bool reset)
|
|
{
|
|
if (reset) {
|
|
syscon->p_reset_ctrl &= ~mask;
|
|
} else {
|
|
syscon->p_reset_ctrl |= mask;
|
|
}
|
|
}
|
|
static void syscon_frg_init(struct lpc11u6x_syscon_regs *syscon)
|
|
{
|
|
uint32_t div;
|
|
|
|
div = CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / LPC11U6X_USART_CLOCK_RATE;
|
|
if (!div) {
|
|
div = 1;
|
|
}
|
|
syscon->frg_clk_div = div;
|
|
|
|
syscon_peripheral_reset(syscon, LPC11U6X_PRESET_CTRL_FRG, false);
|
|
syscon->uart_frg_div = 0xFF;
|
|
syscon->uart_frg_mult = ((CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / div)
|
|
* 256) / LPC11U6X_USART_CLOCK_RATE;
|
|
}
|
|
|
|
static void syscon_frg_deinit(struct lpc11u6x_syscon_regs *syscon)
|
|
{
|
|
syscon->uart_frg_div = 0x0;
|
|
syscon_peripheral_reset(syscon, LPC11U6X_PRESET_CTRL_FRG, true);
|
|
}
|
|
|
|
static int lpc11u6x_clock_control_on(const struct device *dev,
|
|
clock_control_subsys_t sub_system)
|
|
{
|
|
const struct lpc11u6x_syscon_config *cfg = dev->config;
|
|
struct lpc11u6x_syscon_data *data = dev->data;
|
|
uint32_t clk_mask = 0, reset_mask = 0;
|
|
int ret = 0, init_frg = 0;
|
|
|
|
k_mutex_lock(&data->mutex, K_FOREVER);
|
|
|
|
switch ((int) sub_system) {
|
|
case LPC11U6X_CLOCK_I2C0:
|
|
clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_I2C0;
|
|
reset_mask = LPC11U6X_PRESET_CTRL_I2C0;
|
|
break;
|
|
case LPC11U6X_CLOCK_I2C1:
|
|
clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_I2C1;
|
|
reset_mask = LPC11U6X_PRESET_CTRL_I2C1;
|
|
break;
|
|
case LPC11U6X_CLOCK_GPIO:
|
|
clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_GPIO |
|
|
LPC11U6X_SYS_AHB_CLK_CTRL_PINT;
|
|
break;
|
|
case LPC11U6X_CLOCK_USART0:
|
|
cfg->syscon->usart0_clk_div = 1;
|
|
clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART0;
|
|
break;
|
|
case LPC11U6X_CLOCK_USART1:
|
|
if (!data->frg_in_use++) {
|
|
init_frg = 1;
|
|
}
|
|
clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART1;
|
|
reset_mask = LPC11U6X_PRESET_CTRL_USART1;
|
|
break;
|
|
case LPC11U6X_CLOCK_USART2:
|
|
if (!data->frg_in_use++) {
|
|
init_frg = 1;
|
|
}
|
|
clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART2;
|
|
reset_mask = LPC11U6X_PRESET_CTRL_USART2;
|
|
break;
|
|
case LPC11U6X_CLOCK_USART3:
|
|
if (!data->frg_in_use++) {
|
|
init_frg = 1;
|
|
}
|
|
data->usart34_in_use++;
|
|
clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART3_4;
|
|
reset_mask = LPC11U6X_PRESET_CTRL_USART3;
|
|
break;
|
|
case LPC11U6X_CLOCK_USART4:
|
|
if (!data->frg_in_use++) {
|
|
init_frg = 1;
|
|
}
|
|
data->usart34_in_use++;
|
|
clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART3_4;
|
|
reset_mask = LPC11U6X_PRESET_CTRL_USART4;
|
|
break;
|
|
default:
|
|
k_mutex_unlock(&data->mutex);
|
|
return -EINVAL;
|
|
}
|
|
|
|
syscon_ahb_clock_enable(cfg->syscon, clk_mask, true);
|
|
if (init_frg) {
|
|
syscon_frg_init(cfg->syscon);
|
|
}
|
|
syscon_peripheral_reset(cfg->syscon, reset_mask, false);
|
|
k_mutex_unlock(&data->mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int lpc11u6x_clock_control_off(const struct device *dev,
|
|
clock_control_subsys_t sub_system)
|
|
{
|
|
const struct lpc11u6x_syscon_config *cfg = dev->config;
|
|
struct lpc11u6x_syscon_data *data = dev->data;
|
|
uint32_t clk_mask = 0, reset_mask = 0;
|
|
int ret = 0, deinit_frg = 0;
|
|
|
|
k_mutex_lock(&data->mutex, K_FOREVER);
|
|
|
|
switch ((int) sub_system) {
|
|
case LPC11U6X_CLOCK_I2C0:
|
|
clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_I2C0;
|
|
reset_mask = LPC11U6X_PRESET_CTRL_I2C0;
|
|
break;
|
|
case LPC11U6X_CLOCK_I2C1:
|
|
clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_I2C1;
|
|
reset_mask = LPC11U6X_PRESET_CTRL_I2C1;
|
|
break;
|
|
case LPC11U6X_CLOCK_GPIO:
|
|
clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_GPIO |
|
|
LPC11U6X_SYS_AHB_CLK_CTRL_PINT;
|
|
break;
|
|
case LPC11U6X_CLOCK_USART0:
|
|
cfg->syscon->usart0_clk_div = 0;
|
|
clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART0;
|
|
break;
|
|
case LPC11U6X_CLOCK_USART1:
|
|
if (!(--data->frg_in_use)) {
|
|
deinit_frg = 1;
|
|
}
|
|
clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART1;
|
|
reset_mask = LPC11U6X_PRESET_CTRL_USART1;
|
|
break;
|
|
case LPC11U6X_CLOCK_USART2:
|
|
if (!(--data->frg_in_use)) {
|
|
deinit_frg = 1;
|
|
}
|
|
clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART2;
|
|
reset_mask = LPC11U6X_PRESET_CTRL_USART2;
|
|
break;
|
|
case LPC11U6X_CLOCK_USART3:
|
|
if (!(--data->frg_in_use)) {
|
|
deinit_frg = 1;
|
|
}
|
|
if (!(--data->usart34_in_use)) {
|
|
clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART3_4;
|
|
}
|
|
reset_mask = LPC11U6X_PRESET_CTRL_USART3;
|
|
break;
|
|
case LPC11U6X_CLOCK_USART4:
|
|
if (!(--data->frg_in_use)) {
|
|
deinit_frg = 1;
|
|
}
|
|
if (!(--data->usart34_in_use)) {
|
|
clk_mask = LPC11U6X_SYS_AHB_CLK_CTRL_USART3_4;
|
|
}
|
|
reset_mask = LPC11U6X_PRESET_CTRL_USART4;
|
|
break;
|
|
default:
|
|
k_mutex_unlock(&data->mutex);
|
|
return -EINVAL;
|
|
}
|
|
|
|
syscon_ahb_clock_enable(cfg->syscon, clk_mask, false);
|
|
if (deinit_frg) {
|
|
syscon_frg_deinit(cfg->syscon);
|
|
}
|
|
syscon_peripheral_reset(cfg->syscon, reset_mask, true);
|
|
k_mutex_unlock(&data->mutex);
|
|
return ret;
|
|
|
|
}
|
|
|
|
static int lpc11u6x_clock_control_get_rate(const struct device *dev,
|
|
clock_control_subsys_t sub_system,
|
|
uint32_t *rate)
|
|
{
|
|
switch ((int) sub_system) {
|
|
case LPC11U6X_CLOCK_I2C0:
|
|
case LPC11U6X_CLOCK_I2C1:
|
|
case LPC11U6X_CLOCK_GPIO:
|
|
case LPC11U6X_CLOCK_USART0:
|
|
*rate = CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC;
|
|
break;
|
|
case LPC11U6X_CLOCK_USART1:
|
|
case LPC11U6X_CLOCK_USART2:
|
|
case LPC11U6X_CLOCK_USART3:
|
|
case LPC11U6X_CLOCK_USART4:
|
|
*rate = LPC11U6X_USART_CLOCK_RATE;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int lpc11u6x_syscon_init(const struct device *dev)
|
|
{
|
|
const struct lpc11u6x_syscon_config *cfg = dev->config;
|
|
struct lpc11u6x_syscon_data *data = dev->data;
|
|
uint32_t val;
|
|
|
|
k_mutex_init(&data->mutex);
|
|
data->frg_in_use = 0;
|
|
data->usart34_in_use = 0;
|
|
/* Enable SRAM1 and USB ram if needed */
|
|
val = 0;
|
|
#ifdef CONFIG_CLOCK_CONTROL_LPC11U6X_ENABLE_SRAM1
|
|
val |= LPC11U6X_SYS_AHB_CLK_CTRL_SRAM1;
|
|
#endif /* CONFIG_CLOCK_CONTROL_LPC11U6X_ENABLE_SRAM1 */
|
|
#ifdef CONFIG_CLOCK_CONTROL_LPC11U6X_ENABLE_USB_RAM
|
|
val |= LPC11U6X_SYS_AHB_CLK_CTRL_USB_SRAM;
|
|
#endif /* CONFIG_CLOCK_CONTROL_LPC11U6X_ENABLE_USB_RAM */
|
|
|
|
/* Enable IOCON (I/O Control) clock. */
|
|
val |= LPC11U6X_SYS_AHB_CLK_CTRL_IOCON;
|
|
|
|
syscon_ahb_clock_enable(cfg->syscon, val, true);
|
|
|
|
/* Configure PLL output as the main clock source, with a frequency of
|
|
* 48MHz
|
|
*/
|
|
#ifdef CONFIG_CLOCK_CONTROL_LPC11U6X_PLL_SRC_SYSOSC
|
|
syscon_power_up(cfg->syscon, LPC11U6X_PDRUNCFG_SYSOSC_PD, true);
|
|
|
|
/* Wait ~500us */
|
|
for (int i = 0; i < 2500; i++) {
|
|
}
|
|
|
|
/* Configure PLL input */
|
|
syscon_set_pll_src(cfg->syscon, LPC11U6X_SYS_PLL_CLK_SEL_SYSOSC);
|
|
|
|
pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_DEFAULT);
|
|
|
|
#elif defined(CONFIG_CLOCK_CONTROL_LPC11U6X_PLL_SRC_IRC)
|
|
syscon_power_up(cfg->syscon, LPC11U6X_PDRUNCFG_IRC_PD, true);
|
|
syscon_set_pll_src(cfg->syscon, LPC11U6X_SYS_PLL_CLK_SEL_IRC);
|
|
#endif
|
|
/* Flash access takes 3 clock cycles for main clock frequencies
|
|
* between 40MHz and 50MHz
|
|
*/
|
|
set_flash_access_time(LPC11U6X_FLASH_TIMING_3CYCLES);
|
|
|
|
/* Shutdown PLL to change divider/mult ratios */
|
|
syscon_power_up(cfg->syscon, LPC11U6X_PDRUNCFG_PLL_PD, false);
|
|
|
|
/* Setup PLL to have 48MHz output */
|
|
syscon_setup_pll(cfg->syscon, 3, 1);
|
|
|
|
/* Power up pll and wait */
|
|
syscon_power_up(cfg->syscon, LPC11U6X_PDRUNCFG_PLL_PD, true);
|
|
|
|
while (!syscon_pll_locked(cfg->syscon)) {
|
|
}
|
|
|
|
cfg->syscon->sys_ahb_clk_div = 1;
|
|
syscon_set_main_clock_source(cfg->syscon, LPC11U6X_MAIN_CLK_SRC_PLLOUT);
|
|
return 0;
|
|
}
|
|
|
|
static const struct clock_control_driver_api lpc11u6x_clock_control_api = {
|
|
.on = lpc11u6x_clock_control_on,
|
|
.off = lpc11u6x_clock_control_off,
|
|
.get_rate = lpc11u6x_clock_control_get_rate,
|
|
};
|
|
|
|
|
|
PINCTRL_DT_INST_DEFINE(0);
|
|
|
|
static const struct lpc11u6x_syscon_config syscon_config = {
|
|
.syscon = (struct lpc11u6x_syscon_regs *) DT_INST_REG_ADDR(0),
|
|
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0),
|
|
};
|
|
|
|
static struct lpc11u6x_syscon_data syscon_data;
|
|
|
|
DEVICE_DT_INST_DEFINE(0,
|
|
&lpc11u6x_syscon_init,
|
|
NULL,
|
|
&syscon_data, &syscon_config,
|
|
PRE_KERNEL_1, CONFIG_CLOCK_CONTROL_INIT_PRIORITY,
|
|
&lpc11u6x_clock_control_api);
|